From f7e6813254a86ff405e63dc9986388dd870f2d71 Mon Sep 17 00:00:00 2001 From: joan2937 Date: Thu, 12 Nov 2020 21:58:13 +0000 Subject: [PATCH] v0.0 --- CFG/.lg_secret | 7 + CFG/cgi/.git-dummy | 0 CFG/permits | 53 + DOC/HTML/images/LDR-fritz.png | Bin 0 -> 42505 bytes DOC/HTML/images/LDR-gnup-1.png | Bin 0 -> 47487 bytes DOC/HTML/images/LDR-gnup-2.png | Bin 0 -> 31410 bytes DOC/HTML/images/LDR-photo.jpg | Bin 0 -> 109499 bytes DOC/HTML/images/LDR-wave-1.png | Bin 0 -> 4557 bytes DOC/HTML/images/LDR-wave-2.png | Bin 0 -> 4438 bytes DOC/HTML/images/LDR-wave-3.png | Bin 0 -> 4872 bytes DOC/HTML/images/breadboard.jpg | Bin 0 -> 46953 bytes DOC/HTML/images/caps.jpg | Bin 0 -> 35397 bytes DOC/HTML/images/driver.jpg | Bin 0 -> 41058 bytes DOC/HTML/images/faq-i2c-ss.png | Bin 0 -> 9006 bytes DOC/HTML/images/faq-i2c.jpg | Bin 0 -> 42782 bytes DOC/HTML/images/faq-serial.jpg | Bin 0 -> 40297 bytes DOC/HTML/images/faq-spi.jpg | Bin 0 -> 43222 bytes DOC/HTML/images/faq1.jpg | Bin 0 -> 14196 bytes DOC/HTML/images/faq2.jpg | Bin 0 -> 37418 bytes DOC/HTML/images/faq3.jpg | Bin 0 -> 6686 bytes DOC/HTML/images/imu-1.jpg | Bin 0 -> 44650 bytes DOC/HTML/images/imu-2.jpg | Bin 0 -> 42737 bytes DOC/HTML/images/imu-3.jpg | Bin 0 -> 42800 bytes DOC/HTML/images/ir-fritz.png | Bin 0 -> 40477 bytes DOC/HTML/images/ir-motion.jpg | Bin 0 -> 22139 bytes DOC/HTML/images/ir-photo.jpg | Bin 0 -> 90482 bytes DOC/HTML/images/ir-rx.jpg | Bin 0 -> 32358 bytes DOC/HTML/images/ir-wave-1.png | Bin 0 -> 4827 bytes DOC/HTML/images/ir-wave-2.png | Bin 0 -> 4237 bytes DOC/HTML/images/ir-wave-3.png | Bin 0 -> 5038 bytes DOC/HTML/images/keypad.jpg | Bin 0 -> 30294 bytes DOC/HTML/images/lcd.jpg | Bin 0 -> 29926 bytes DOC/HTML/images/ldr-cap.jpg | Bin 0 -> 34448 bytes DOC/HTML/images/ldr.jpg | Bin 0 -> 28458 bytes DOC/HTML/images/leds.jpg | Bin 0 -> 36146 bytes DOC/HTML/images/lg-logo.gif | Bin 0 -> 375 bytes DOC/HTML/images/meter.jpg | Bin 0 -> 29610 bytes DOC/HTML/images/motor.jpg | Bin 0 -> 17593 bytes DOC/HTML/images/msp430.jpg | Bin 0 -> 43686 bytes DOC/HTML/images/nano.jpg | Bin 0 -> 40463 bytes DOC/HTML/images/oled-2.jpg | Bin 0 -> 40888 bytes DOC/HTML/images/oled.jpg | Bin 0 -> 31872 bytes DOC/HTML/images/pins.jpg | Bin 0 -> 42788 bytes DOC/HTML/images/pisc-1.jpg | Bin 0 -> 20520 bytes DOC/HTML/images/pisc-2.jpg | Bin 0 -> 17527 bytes DOC/HTML/images/pisc-3.jpg | Bin 0 -> 15915 bytes DOC/HTML/images/pot.jpg | Bin 0 -> 26518 bytes DOC/HTML/images/pro-mini.jpg | Bin 0 -> 36398 bytes DOC/HTML/images/psu.jpg | Bin 0 -> 33988 bytes DOC/HTML/images/re-fritz.png | Bin 0 -> 39702 bytes DOC/HTML/images/re-photo.jpg | Bin 0 -> 50100 bytes DOC/HTML/images/re-wave-1.png | Bin 0 -> 5570 bytes DOC/HTML/images/re-wave-2.png | Bin 0 -> 5404 bytes DOC/HTML/images/remote-1.jpg | Bin 0 -> 27199 bytes DOC/HTML/images/remote-2.jpg | Bin 0 -> 28482 bytes DOC/HTML/images/reverse.jpg | Bin 0 -> 35860 bytes DOC/HTML/images/rf-rx-2.jpg | Bin 0 -> 34515 bytes DOC/HTML/images/rf-rx.jpg | Bin 0 -> 31082 bytes DOC/HTML/images/rf-tx.jpg | Bin 0 -> 40421 bytes DOC/HTML/images/rotary.jpg | Bin 0 -> 22765 bytes DOC/HTML/images/rpi.jpg | Bin 0 -> 37698 bytes DOC/HTML/images/serial.jpg | Bin 0 -> 29831 bytes DOC/HTML/images/servo.jpg | Bin 0 -> 28811 bytes DOC/HTML/images/sidebar.gif | Bin 0 -> 52 bytes DOC/HTML/images/son-fritz.png | Bin 0 -> 69915 bytes DOC/HTML/images/son-gnup-1.png | Bin 0 -> 15447 bytes DOC/HTML/images/son-gnup-2.png | Bin 0 -> 12101 bytes DOC/HTML/images/son-photo.jpg | Bin 0 -> 69827 bytes DOC/HTML/images/son-wave-1.png | Bin 0 -> 4410 bytes DOC/HTML/images/son-wave-2.png | Bin 0 -> 4293 bytes DOC/HTML/images/son-wave-3.png | Bin 0 -> 4757 bytes DOC/HTML/images/son-wave-4.png | Bin 0 -> 5270 bytes DOC/HTML/images/speaker.jpg | Bin 0 -> 34456 bytes DOC/HTML/images/spi-lnx-pi3b.png | Bin 0 -> 9134 bytes DOC/HTML/images/spi-lnx-pibr1.png | Bin 0 -> 7174 bytes DOC/HTML/images/spi-pig-pi3b.png | Bin 0 -> 8896 bytes DOC/HTML/images/spi-pig-pibr1.png | Bin 0 -> 8502 bytes DOC/HTML/images/srf02.jpg | Bin 0 -> 26595 bytes DOC/HTML/images/srf04.jpg | Bin 0 -> 33664 bytes DOC/HTML/images/stepper.jpg | Bin 0 -> 30634 bytes DOC/HTML/images/switches.jpg | Bin 0 -> 43275 bytes DOC/HTML/images/topbar.gif | Bin 0 -> 1015 bytes DOC/HTML/images/transistors.jpg | Bin 0 -> 43174 bytes DOC/HTML/images/ubec-2.jpg | Bin 0 -> 18920 bytes DOC/HTML/images/uln2003a.jpg | Bin 0 -> 26817 bytes DOC/HTML/images/wheel.jpg | Bin 0 -> 23241 bytes DOC/HTML/images/wires.jpg | Bin 0 -> 36804 bytes DOC/HTML/images/yl-40.jpg | Bin 0 -> 43771 bytes DOC/HTML/scripts/index.css | 52 + DOC/HTML/scripts/standard.css | 3 + DOC/README | 16 + DOC/bin/backup.sh | 5 + DOC/bin/body.py | 14 + DOC/bin/build_site.py | 21 + DOC/bin/cmakdoc.py | 534 +++ DOC/bin/dmakdoc.py | 329 ++ DOC/bin/examples.py | 123 + DOC/bin/html.py | 139 + DOC/bin/purge.sh | 14 + DOC/bin/pymakdoc.py | 314 ++ DOC/bin/smakdoc.py | 380 ++ DOC/bin/tidy.py | 28 + DOC/bin/updatesql.py | 21 + DOC/cdoc | 24 + DOC/dbase/lg.sqlite | Bin 0 -> 1433600 bytes DOC/hdoc | 42 + DOC/makedoc | 6 + DOC/pdoc | 13 + DOC/src/defs/download.def | 76 + DOC/src/defs/examples.def | 54 + DOC/src/defs/faq.def | 4 + DOC/src/defs/index.def | 46 + DOC/src/defs/permits.def | 313 ++ DOC/src/defs/rgpiod.def | 110 + DOC/src/defs/rgs.def | 1982 +++++++++++ DOC/src/defs/scripts.def | 97 + EXAMPLES/lgpio/bench.c | 40 + EXAMPLES/lgpio/chipline.c | 32 + EXAMPLES/lgpio/dhtxx.c | 257 ++ EXAMPLES/lgpio/tx_pulse.c | 41 + EXAMPLES/lgpio/tx_wave.c | 54 + EXAMPLES/py_lgpio/DHT.py | 227 ++ EXAMPLES/py_lgpio/bench.py | 24 + EXAMPLES/py_lgpio/chipline.py | 16 + EXAMPLES/py_lgpio/errors.py | 359 ++ EXAMPLES/py_lgpio/q0.py | 21 + EXAMPLES/py_lgpio/q1.py | 19 + EXAMPLES/py_lgpio/q2.py | 21 + EXAMPLES/py_lgpio/testbed.py | 82 + EXAMPLES/py_lgpio/tx_pulse.py | 29 + EXAMPLES/py_lgpio/tx_wave.py | 36 + EXAMPLES/py_rgpio/DHT.py | 234 ++ EXAMPLES/py_rgpio/DS18B20.py | 74 + EXAMPLES/py_rgpio/bench.py | 30 + EXAMPLES/py_rgpio/chipline.py | 20 + EXAMPLES/py_rgpio/errors.py | 465 +++ EXAMPLES/py_rgpio/files.py | 185 + EXAMPLES/py_rgpio/testbed.py | 86 + EXAMPLES/py_rgpio/tx_pulse.py | 33 + EXAMPLES/py_rgpio/tx_wave.py | 42 + EXAMPLES/rgpio/DS18B20.c | 115 + EXAMPLES/rgpio/bench.c | 52 + EXAMPLES/rgpio/chipline.c | 46 + EXAMPLES/rgpio/dhtxxd.c | 589 ++++ EXAMPLES/rgpio/errors.c | 487 +++ EXAMPLES/rgpio/files.c | 145 + EXAMPLES/rgpio/tx_pulse.c | 53 + EXAMPLES/rgpio/tx_wave.c | 66 + EXAMPLES/rgs/files | 11 + Makefile | 199 ++ PY_LGPIO/lgpio.i | 749 ++++ PY_LGPIO/lgpio_extra.py | 2249 ++++++++++++ PY_LGPIO/setup.py | 16 + PY_RGPIO/rgpio.py | 3761 ++++++++++++++++++++ PY_RGPIO/setup.py | 23 + README | 94 + README.md | 43 + UNLICENCE | 25 + lgCfg.c | 456 +++ lgCfg.h | 54 + lgCmd.c | 1086 ++++++ lgCmd.h | 127 + lgCtx.c | 72 + lgCtx.h | 61 + lgDbg.c | 124 + lgDbg.h | 86 + lgErr.c | 156 + lgExec.c | 1169 +++++++ lgFile.c | 317 ++ lgGpio.c | 1519 ++++++++ lgGpio.h | 70 + lgHdl.c | 440 +++ lgHdl.h | 66 + lgI2C.c | 1093 ++++++ lgMD5.c | 313 ++ lgMD5.h | 55 + lgNotify.c | 279 ++ lgPthAlerts.c | 654 ++++ lgPthAlerts.h | 66 + lgPthSocket.c | 231 ++ lgPthTx.c | 319 ++ lgPthTx.h | 86 + lgSPI.c | 218 ++ lgScript.c | 784 +++++ lgSerial.c | 295 ++ lgThread.c | 88 + lgUtil.c | 244 ++ lgpio.3 | 5184 +++++++++++++++++++++++++++ lgpio.h | 2947 ++++++++++++++++ rgpio.3 | 5416 +++++++++++++++++++++++++++++ rgpio.c | 1776 ++++++++++ rgpio.h | 2933 ++++++++++++++++ rgpiod.1 | 1056 ++++++ rgpiod.c | 303 ++ rgpiod.h | 283 ++ rgs.1 | 3712 ++++++++++++++++++++ rgs.c | 568 +++ 197 files changed, 50676 insertions(+) create mode 100644 CFG/.lg_secret create mode 100644 CFG/cgi/.git-dummy create mode 100644 CFG/permits create mode 100644 DOC/HTML/images/LDR-fritz.png create mode 100644 DOC/HTML/images/LDR-gnup-1.png create mode 100644 DOC/HTML/images/LDR-gnup-2.png create mode 100644 DOC/HTML/images/LDR-photo.jpg create mode 100644 DOC/HTML/images/LDR-wave-1.png create mode 100644 DOC/HTML/images/LDR-wave-2.png create mode 100644 DOC/HTML/images/LDR-wave-3.png create mode 100644 DOC/HTML/images/breadboard.jpg create mode 100644 DOC/HTML/images/caps.jpg create mode 100644 DOC/HTML/images/driver.jpg create mode 100644 DOC/HTML/images/faq-i2c-ss.png create mode 100644 DOC/HTML/images/faq-i2c.jpg create mode 100644 DOC/HTML/images/faq-serial.jpg create mode 100644 DOC/HTML/images/faq-spi.jpg create mode 100644 DOC/HTML/images/faq1.jpg create mode 100644 DOC/HTML/images/faq2.jpg create mode 100644 DOC/HTML/images/faq3.jpg create mode 100644 DOC/HTML/images/imu-1.jpg create mode 100644 DOC/HTML/images/imu-2.jpg create mode 100644 DOC/HTML/images/imu-3.jpg create mode 100644 DOC/HTML/images/ir-fritz.png create mode 100644 DOC/HTML/images/ir-motion.jpg create mode 100644 DOC/HTML/images/ir-photo.jpg create mode 100644 DOC/HTML/images/ir-rx.jpg create mode 100644 DOC/HTML/images/ir-wave-1.png create mode 100644 DOC/HTML/images/ir-wave-2.png create mode 100644 DOC/HTML/images/ir-wave-3.png create mode 100644 DOC/HTML/images/keypad.jpg create mode 100644 DOC/HTML/images/lcd.jpg create mode 100644 DOC/HTML/images/ldr-cap.jpg create mode 100644 DOC/HTML/images/ldr.jpg create mode 100644 DOC/HTML/images/leds.jpg create mode 100644 DOC/HTML/images/lg-logo.gif create mode 100644 DOC/HTML/images/meter.jpg create mode 100644 DOC/HTML/images/motor.jpg create mode 100644 DOC/HTML/images/msp430.jpg create mode 100644 DOC/HTML/images/nano.jpg create mode 100644 DOC/HTML/images/oled-2.jpg create mode 100644 DOC/HTML/images/oled.jpg create mode 100644 DOC/HTML/images/pins.jpg create mode 100644 DOC/HTML/images/pisc-1.jpg create mode 100644 DOC/HTML/images/pisc-2.jpg create mode 100644 DOC/HTML/images/pisc-3.jpg create mode 100644 DOC/HTML/images/pot.jpg create mode 100644 DOC/HTML/images/pro-mini.jpg create mode 100644 DOC/HTML/images/psu.jpg create mode 100644 DOC/HTML/images/re-fritz.png create mode 100644 DOC/HTML/images/re-photo.jpg create mode 100644 DOC/HTML/images/re-wave-1.png create mode 100644 DOC/HTML/images/re-wave-2.png create mode 100644 DOC/HTML/images/remote-1.jpg create mode 100644 DOC/HTML/images/remote-2.jpg create mode 100644 DOC/HTML/images/reverse.jpg create mode 100644 DOC/HTML/images/rf-rx-2.jpg create mode 100644 DOC/HTML/images/rf-rx.jpg create mode 100644 DOC/HTML/images/rf-tx.jpg create mode 100644 DOC/HTML/images/rotary.jpg create mode 100644 DOC/HTML/images/rpi.jpg create mode 100644 DOC/HTML/images/serial.jpg create mode 100644 DOC/HTML/images/servo.jpg create mode 100644 DOC/HTML/images/sidebar.gif create mode 100644 DOC/HTML/images/son-fritz.png create mode 100644 DOC/HTML/images/son-gnup-1.png create mode 100644 DOC/HTML/images/son-gnup-2.png create mode 100644 DOC/HTML/images/son-photo.jpg create mode 100644 DOC/HTML/images/son-wave-1.png create mode 100644 DOC/HTML/images/son-wave-2.png create mode 100644 DOC/HTML/images/son-wave-3.png create mode 100644 DOC/HTML/images/son-wave-4.png create mode 100644 DOC/HTML/images/speaker.jpg create mode 100644 DOC/HTML/images/spi-lnx-pi3b.png create mode 100644 DOC/HTML/images/spi-lnx-pibr1.png create mode 100644 DOC/HTML/images/spi-pig-pi3b.png create mode 100644 DOC/HTML/images/spi-pig-pibr1.png create mode 100644 DOC/HTML/images/srf02.jpg create mode 100644 DOC/HTML/images/srf04.jpg create mode 100644 DOC/HTML/images/stepper.jpg create mode 100644 DOC/HTML/images/switches.jpg create mode 100644 DOC/HTML/images/topbar.gif create mode 100644 DOC/HTML/images/transistors.jpg create mode 100644 DOC/HTML/images/ubec-2.jpg create mode 100644 DOC/HTML/images/uln2003a.jpg create mode 100644 DOC/HTML/images/wheel.jpg create mode 100644 DOC/HTML/images/wires.jpg create mode 100644 DOC/HTML/images/yl-40.jpg create mode 100644 DOC/HTML/scripts/index.css create mode 100644 DOC/HTML/scripts/standard.css create mode 100644 DOC/README create mode 100755 DOC/bin/backup.sh create mode 100755 DOC/bin/body.py create mode 100755 DOC/bin/build_site.py create mode 100755 DOC/bin/cmakdoc.py create mode 100755 DOC/bin/dmakdoc.py create mode 100755 DOC/bin/examples.py create mode 100755 DOC/bin/html.py create mode 100755 DOC/bin/purge.sh create mode 100755 DOC/bin/pymakdoc.py create mode 100755 DOC/bin/smakdoc.py create mode 100755 DOC/bin/tidy.py create mode 100755 DOC/bin/updatesql.py create mode 100755 DOC/cdoc create mode 100644 DOC/dbase/lg.sqlite create mode 100755 DOC/hdoc create mode 100755 DOC/makedoc create mode 100755 DOC/pdoc create mode 100644 DOC/src/defs/download.def create mode 100644 DOC/src/defs/examples.def create mode 100644 DOC/src/defs/faq.def create mode 100644 DOC/src/defs/index.def create mode 100644 DOC/src/defs/permits.def create mode 100644 DOC/src/defs/rgpiod.def create mode 100644 DOC/src/defs/rgs.def create mode 100644 DOC/src/defs/scripts.def create mode 100644 EXAMPLES/lgpio/bench.c create mode 100644 EXAMPLES/lgpio/chipline.c create mode 100644 EXAMPLES/lgpio/dhtxx.c create mode 100644 EXAMPLES/lgpio/tx_pulse.c create mode 100644 EXAMPLES/lgpio/tx_wave.c create mode 100755 EXAMPLES/py_lgpio/DHT.py create mode 100755 EXAMPLES/py_lgpio/bench.py create mode 100755 EXAMPLES/py_lgpio/chipline.py create mode 100755 EXAMPLES/py_lgpio/errors.py create mode 100644 EXAMPLES/py_lgpio/q0.py create mode 100644 EXAMPLES/py_lgpio/q1.py create mode 100644 EXAMPLES/py_lgpio/q2.py create mode 100755 EXAMPLES/py_lgpio/testbed.py create mode 100755 EXAMPLES/py_lgpio/tx_pulse.py create mode 100755 EXAMPLES/py_lgpio/tx_wave.py create mode 100755 EXAMPLES/py_rgpio/DHT.py create mode 100755 EXAMPLES/py_rgpio/DS18B20.py create mode 100755 EXAMPLES/py_rgpio/bench.py create mode 100755 EXAMPLES/py_rgpio/chipline.py create mode 100755 EXAMPLES/py_rgpio/errors.py create mode 100755 EXAMPLES/py_rgpio/files.py create mode 100755 EXAMPLES/py_rgpio/testbed.py create mode 100755 EXAMPLES/py_rgpio/tx_pulse.py create mode 100755 EXAMPLES/py_rgpio/tx_wave.py create mode 100644 EXAMPLES/rgpio/DS18B20.c create mode 100644 EXAMPLES/rgpio/bench.c create mode 100644 EXAMPLES/rgpio/chipline.c create mode 100644 EXAMPLES/rgpio/dhtxxd.c create mode 100644 EXAMPLES/rgpio/errors.c create mode 100644 EXAMPLES/rgpio/files.c create mode 100644 EXAMPLES/rgpio/tx_pulse.c create mode 100644 EXAMPLES/rgpio/tx_wave.c create mode 100755 EXAMPLES/rgs/files create mode 100644 Makefile create mode 100644 PY_LGPIO/lgpio.i create mode 100644 PY_LGPIO/lgpio_extra.py create mode 100644 PY_LGPIO/setup.py create mode 100644 PY_RGPIO/rgpio.py create mode 100644 PY_RGPIO/setup.py create mode 100644 README create mode 100644 README.md create mode 100644 UNLICENCE create mode 100644 lgCfg.c create mode 100644 lgCfg.h create mode 100644 lgCmd.c create mode 100644 lgCmd.h create mode 100644 lgCtx.c create mode 100644 lgCtx.h create mode 100644 lgDbg.c create mode 100644 lgDbg.h create mode 100644 lgErr.c create mode 100644 lgExec.c create mode 100644 lgFile.c create mode 100644 lgGpio.c create mode 100644 lgGpio.h create mode 100644 lgHdl.c create mode 100644 lgHdl.h create mode 100644 lgI2C.c create mode 100644 lgMD5.c create mode 100644 lgMD5.h create mode 100644 lgNotify.c create mode 100644 lgPthAlerts.c create mode 100644 lgPthAlerts.h create mode 100644 lgPthSocket.c create mode 100644 lgPthTx.c create mode 100644 lgPthTx.h create mode 100644 lgSPI.c create mode 100644 lgScript.c create mode 100644 lgSerial.c create mode 100644 lgThread.c create mode 100644 lgUtil.c create mode 100644 lgpio.3 create mode 100644 lgpio.h create mode 100644 rgpio.3 create mode 100644 rgpio.c create mode 100644 rgpio.h create mode 100644 rgpiod.1 create mode 100644 rgpiod.c create mode 100644 rgpiod.h create mode 100644 rgs.1 create mode 100644 rgs.c diff --git a/CFG/.lg_secret b/CFG/.lg_secret new file mode 100644 index 0000000..0ff841e --- /dev/null +++ b/CFG/.lg_secret @@ -0,0 +1,7 @@ +# lg user file +# user=password +admin=kr6g89XmFQvLDWh6UcJH +test1=fARrxSKqdHaPHBu6Vtet +test2=t4pf4kvPOXjLfDnKBrMu +test3=tugXUuRdPqGux6t7jhhv + diff --git a/CFG/cgi/.git-dummy b/CFG/cgi/.git-dummy new file mode 100644 index 0000000..e69de29 diff --git a/CFG/permits b/CFG/permits new file mode 100644 index 0000000..52407f1 --- /dev/null +++ b/CFG/permits @@ -0,0 +1,53 @@ +# rgpiod test file for user access +# user=permission + +[files] +default=: +test1=/tmp/* u:* n:file* r:test.file u:/sys/bus/w1/devices/28*/w1_slave r + +[gpio] +default=0.2-27 +test1=*.2-27 +test2=0.2-27 +test3=0.5-10 + +[i2c] +default=1.* +test1=1-999.* +test2=1-2.* +test3=2.5-20 + +[notify] +default=n +test1=n +test2=y +test3=y + +[scripts] +default=n +test1=y +test2=n +test3=y + +[serial] +default=serial0 +test1=serial*:ttyUSB*:ttyS* +test2=ttyUSB1:tty0:ttyS0 +test3=null + +[spi] +default=0.* +test1=0.0:0.1:1.0:1.1:1.2:2.0:2.1 +test2=0.* +test3=*.0 + +[debug] +default=n +admin=y + +[shell] +default=n +test1=n +test2=n +test3=y + diff --git a/DOC/HTML/images/LDR-fritz.png b/DOC/HTML/images/LDR-fritz.png new file mode 100644 index 0000000000000000000000000000000000000000..62655853dc5e85e94e128fb89191f05265dbbf58 GIT binary patch literal 42505 zcmXtg2RzmP_kW4ZByPyZCa$e)+4I_aZ$(!2p4r)4GPCzek*v%cl4K`@Bv~P|?Emro zJ^r^x@xJ#x?)&|Ez0Nt$af;DYSG++$MR4WHl^aSZIc@m);mVb(oOoF98-Awa8vM9! zt)eJ*<>K;BL3?@XlSSB)NDxk7(M zNlseVcjkAlU*_$}mMzB_y~Y*4bw64uDQeAYjF}{nPpML#DkDaj6-bAb+%=rtx5Qai zSZsN3;bNyf4SUZ<98H!GMs(wYn8URj```G*H~r=t9p8lnwjv#Unr<+%u$m1Q-#p%_ zHV&AYss7@>VYU!0#Lw?a{g{_hl_hoBNgsiO;U&TpjoPuK4lKKK4jOZkrw(9WSZJl8 zn9+mFPwNdi6^d_ZrJ$H|hOlfJjT01#vwsM4XACauEV>Hk4DE{LYedR0G?L zSL@YHJxWmyHx5XYnMrqiPfIh*XWscW4B3XyUN2N-!R(!83pl=YnwBAn=wG@=NhF;3 zkdi#9e`)96@xexu^W3_7OTk|f?9e4&_UEMXJ1r}Dv#nknf$pgT5lg~}OQUxDDX4e< ze#G6laYG`1_|bg^_AJ$1u}=5pk5N(A<9e-M8-B9oi?R|p8F!k#fGzW0gmf%k7$Vd_78vYqL^V&^@CJioubE`IYS&5Ba4cgZbr>FKV2`=1cgaFrP^9 zI8EOE{*c1-e!e|`EarU*lj2BjA8&jSlDgl-#AIp5S5`K>e<}3*DuYq_-|dBb6f-fc zSOu-`_om628LGJ6r!uT1_M$mMv?A`zDX8DGO&a3-_lb&;%5XF%`D~+O#_Z;GWi3Q@ z8QjO~hyA26?_Zj=)#z7q4w8&*9Mp3*H8#pKricd|Rx^vL&MS0kuk;2&Q1yuWu;K8h3GvzJaiQf{P)KyV=z)8 z!@O>L_44(se`&VKWlA%Q{!LR8Zg_WJpF;U~Wm$GkPFq_W%$Co&4>rBqn3YC|8m3bG z9udY#&}5iZ(ZV7>YNj(Jgqd`0dz-c6c(SfAzWZ= zOgnEyAg$NB<@|U9UOJT%VK{`R!IDH6$|w=)MA)ULM@E{&x&1&t8WSobnDb=jjs5N0 zx3g4#ZMAJ5`nRt({qz)tBhP|6nUy?#eR30~CPCXwJ2wqy-Yc%Cc(&9WNk~x@qM}~z z0gJ}Gjso)v?q_<_TL2aTXHq|`?>V1^%^lw>-EQC3JQL|8=a=1YVecH&g_s3#m5=)k zay3cuyxaTyV9vQIJ6qVTXvnr0x!(7mi>?lF^is~kDNFqR-pa5~+uX=OXg0q+dfLg> z=rHO(XIIhOb;mikei|Z-CgPyhB}hdL?r0#D^*@NQqrP)qQ}6`;eW>Sbo7+0tTS;ZM ze{J|j6Dda$!K{>}%91lww2gyOLO(M4Y}-KHUzk)lwPEF(%FLFl?m?ZQp@e?#_97eN z?4LKJ*jH^`kl0DgS_m5>+Sng{w0Y&QDXD~((~1zuzuXC18bYKM(znJD7FsANwiR>Q zs9glPJ9T;4cRy^VYI;207&!({t{d{`l5R^&BXVS>+%KJJC&AQ#XGIld-;KqFA2%3z z@kJFOW8rkX*jRT3iTV^t|5<-4C8e2Nq=4kN7Ks{0M8~Aj5k0+57TUskEP?;tS_kC$0 zw=xRNoLO;$D$MUWe6B(*5V<7mx037gvHpc}=_4bBR=UI`)Heuq1t0AB6_n80l?Xe> zX9U;7TTGobm4jrc`$q{77+wLKo`P%{27yv!Y&KTARPDVY5+$@9P8Cv_p5*C*_Wdk5 zQYDGfEvb4=+;C~RMd6oa6ATAQB$3$CD3(-g=>l6j83$!_i6t*GmN8-?0rxgSi(jqy zge$fvTU&@$9q;dhEYY4zF=qfHuL(N<`cw6s#XEv4?O@>8jmk5?Pi z1@8?Bad2>qqRIMq6NUv4(*4bcycb>&OUi@TspM1rs|}0=HSK=gQ|?R~cU& ztZ*%Usxu7Tx$B?r(nW=ZS23?{O8mPrKuP~#lz(nwf_ZSaOf5%QSy|J<)+pVIvf!=D z0&y&1k*DY!EN>g5`BpDSH@AE1gMWTKntEv#TjVtRR;~80AtyOZ>wF0=yq?3&*;_EZ zM(utIZ*_%UtLJo?uJ2vhE0q51(1>Q99vkb~JB7Rz8yibvb3g0H&cD%O#k)!n`cfCR z4dMdmPu;1TLN3lBHp&@src2Sgp8j*wLO?O2tEu@kPujbU2J`Ce+qbGL9{>IBxQpCab`@q;T3=m7m1-v>B;erSJgcAGgcl7BO_r+d?As?2`8T!H%Tt${ z`5u2}CXLZ4S3f!nW6CNcttjCd`%3V zuZ4w$80|uPK-1KQd>Z8AdwkTy`63>D8wZRZH@(B9sAA*}w->f|d`Xsfe2-5~!n-Yb zDXA$br5IlnA!(ByFLVTr+P$f(OZxZGpu<`)r><>Fv~!_uOT4tS)XU3@JZ?GN#95l@ z4&HKOW21pZP~)^y-hG=`_yX%^# zkT7p}Ek8F%yu;V$BnWvzb}lBgsBDX(TXTTF@9 zf?ZcEw@UFtlksj!KxwW|xL8$$w7q{PXQ-@jf>pOVlBonLd*k(pfGgGoL$*4EF}%(g z{7vtG4k?0+SH`tR2xOLbn|Q8zHIBCLc+cLuNr%Y~Neaa&D@Uf>j~k}HFPCLwosC$( zwnI)3-N0cesL^*6&HWM~oJfYmh!kMniUW(uS;;WP3O0;E>lVHyH z8b3kzV7I5}ja*f=ZV$od%H0WrFbNUTB)$Ow50ucau1wTWT8UiIk=fsq-#*6+qfC=2 zj8nl6E4ang`HTd^E1YN{g;)0S=p!?7LTvaZ6;)L0tC4~!nT;qb^$&aR{Ev=~Dz!pA z_bG3q6q7>eLlM935Me4J?+RNQ7ffx~XX_{V{;?)~=Wgk*r9;xn3$-EyrW-gKZV=Xr zip!XlptcOx4$p&2Wybx~VZy&dbUneYlGr{%4li_o)xs@uqyX_LU{@BSW0a_akRM)` z6n{>cD8q2Du1FJRGo%T>;3|1$sb0tMy3fD!+wrAl5l0PUw=mKsv6ABdysgWTI`D{8 zYn}EEK_qDmwkDN09|_*m5KTmHU?>8axP%ubwOd?-gko{clL{GIG@;E}p&X$-F$+V(3b*9*qv4SGdYiau zC+`+h2i!_zY_)zZr;l z1^s2e;G^ATDAVXX_|>XuN?=sq6<9uM1Ug`o>!!pj-?`%hlmEP(re6naci$9xGQa!lzq-!9O+HoC z?6E2wOpBWQR6*;N<%hqgb&QM#c!|)W(!-{W4wL6+CxA-2Z-!%!X(Hb^P1m;v{FOm% z(%*>2z=II=T-6K;YM=3;B8Y_Onh(j7NimTmnwSO)=jH$GrKz%8yxB#}NzJASAE+wXuaB7$y8OK&IE`SVH zp1x+7uEvtes*wlP2`(|UR3D16)r}hP(6e6a%k2)a0+~Wh6%`fs=Q9nDlLnV%h_(O4 z=jP^)+U@^Y#Gur}`{gVw=vK1;^e7-WtL_nPz_AL&bV2(mGqn!8R(PHEn;}8Ket&9N5w@QCRJ|#!fBQDB){;)>)?B z@%ZhG`U~N!i7T?v#0*13o-?k(-)3hY@bZq#&D9>}&N#gVc*l#R6ZhAxeFIRYqod>e zJoukbMgKEV|9v}7azbpZ_W<6;*Y;nCctE8vvNT}*TGzlJiB2+askv>dm9QqJf^Oo= zm(Q8^m~z!a_Hq_<1EjlsL?L}9E^WQrQ@C}8v2(7AQ3 z?BhpeqjU-`;}UPK2h@r89`<#q5NeugvA#8~UHI201UJY_)V7irFac~1j7=aQJhgGC z9&~jI;=&luHUqW<%8i@VBy$A5{G$_k;6idP$aXm6;h|Q?&F$;uwx|+E5G+0`{+A%< z)l$4wo;R&)+$p;+IKF&2!G3zp@=)sIyjlL6Kcg+IZ);XE{6wNHKYts)9|Y z%&=A;^19#dqqez3WchfDWj9uM_jmV}OE@*o|L*F9FzUcxTwd!NYjmG<)%aT1Sw)JT zRg^Fh4h2z6V%G| zsK^Pe@D#1pmrmLC)Fcp~HLhqOM~(cEx2L)(>%~q^Z75`+9xdtN6~)Ig+#{JIZu6>O z)K_}3tgDgVT=UaYk#5OndaQf`d^YeTvJ$oKYvqktonN$ z;lR^O>Ms8EFBEe}R+2BFc=(X?J&T$h`@}7pAavXvsa!i`R}t96Zb*%L!;BYaHY~bu zqKA~AJK%L3O4^Rn;R6Yel{I;>8dFO*0nk_}iTm2#G~$^Q2;1PJsbet)>iz+k?kZXc z-=KuU944NMA|zz8x9N`W3ZyE7%IUp&hQ0`+M)f^~3l;Qn9@unjBc8}dll|g&`Aqvn z{T5DP;0DXTs=c7{bz8ePIs&E6@1j07IWsD6KEUBY64=+|SpLISscPL>c~?NGB-kEy zYXT*jUW1&b)=0?WuukjygAb40%8u$Lu{J{6vAo4pU9Ivz*uYECul9l*a6SC0_p|Eu z0-pB$F(AGmJIrqC6P8f-!?sGu@pwKUCJ3cT{eV;eeVO|%MndfdWCYSsqQH352v>5Mb|sJsH5Q#6B_;1|8yH4OzZk)`QJZ(|@Jcv>)WHGxFu0DkpI?hHJx*!B z^2g_wn^3|Tzlm2U{=Oc#**L|{!6AUf*6%i$&J_TgA3uJa$XQOQ-b)aguc)jXpg`sO z{)iEp_r3O0v2h#W{r+MqE$SODPU{7o2v70Qnw5Iu-Q)JIyj+naqO6QXVA)d?s5$^SkBsJ_IDny= znPiK#4hae2&TzE1X9L13ZE)Ga*_jp(+fbyEES2fS#wv=M3D0rMiDhteq0?g1?cgZz zwIL@}0&pes^XB^p?6E%%{3VZ9nS5tGR!10q>i4>R+x`n=!iVNt1d++o23m;Dg^<+| zpr6TN(Xf^Yvw&|bBT?h4EL4pt`!An9F1|CebQ9PmR(lY{3CXdr}G=s-P~SY$KhjRW5>t- zz;ZUB* zdT#>=jEszgszZO+o9H?Qz8Xxp@wHFoENa=hrk5`<`!QxOG+lVNE8LD~?q^u9Frchi zs$uv#OvXKfesR6%ZTd(9cgvm$&DbI+XRQDimhFUPtFkDe>w!bXVO1?3$LJjGGCjI} zg^Xp4(&{$?n;~Nm!1ig-zN<|2WYz!n{@}`w#0fa?*XGZtwvfNdsj9Nk@reZ6gg`=#{C8&DF!Psf1F9Y^O zti19)9G%W*$2`mu)uVhVYP!W?#M}Bbc~o=O7fEID+7pLQOft`d9+Lg{IyPrjY2g-= zS@%#U#ZtSle0Y5+l7M9g`-$h!KQ;AO(iX{q8#(UR{E`3W;E_J2uzdc~d(S@&^Vxy{sh!N7Av#iN`iM$`hCalgL zTZ39rU`cJFzDe8fDwd@RgE)k7eDm;ZymO%2&F~ALA%mDP$|$SY8ssi(HQy#C(S}l9 zizGNkfs;Fjrv32+B@H2_S@qY$y;No!zO0mZap5SX{7=>H)crOM8FueUxvh9;#U)l4 z!ajvA)idL_e8X#d21GuMycV^{fk={;4@PpVSo4gIsoYhy)Wxjo;hTpEw8b=z`;?o8 z#r<~uRN@Lrqlp6k|GS|dwE1X!1xj};kDDkjsSEg|Co566S1>`hD$ zn{mN#(#K3>ytUkQ+54xfKZEv1)U#g^`aSTlzh3pf?<_%Z!AkqukZ4kIDBl@Fr@5)R z>3VQ;^tB}!A%=Hdbe>M*c+6g!#4vp_#6N)`N5(3EKc}f8f`*15H zPh0A7mWzl^dwwbbeBIIfXb7S@=eB;RU&Qt$*KaKA6d}vT?V%_E1w+1A%P5@zm*e=q zS8Vsl9^l|2rx~UoC)eI-{=Br9s-%?87T#@c`nzLGwB_Bqhsz(is~dsu-JH0gh+!>F z9gstJ#ncFFkOdMqT(jX$kYgMc|N9N3%j&ACi6eDP${)5PUG@}4UPi~^W53QJCrp}0 z&T@Xxq0tBFDsDVbQ=xE3=NZ0IZ}$Ja02UpOhx35#i?aIearHLQb=Z0O8ZNO^Em96J zD?p|i!`D9m8^4bsV}(rQ;n6VXtk_O^IGQgW0BQ`>NLQXq_3#pnoeBsDfDi{Lx488S z$l?LO66msf{rqst3!fQDw=gz_9(V-mW(O~WG`)5T_Ltor!9Dtqc! zz`^POUTJQDZjg%$PgN+6uEM91UQc7y@_M#DjOA|s-kqk|To7<|#nVi*GrS$1Jc)dV zDOb;RZ=1^=Q%^2qAdRVTi3PgTYh9Ny<(VAg`=*Wqu3Ysd%kD6!3%ov#R8>_$ZB@^C z(sk{o6uTVWJt7@FJpq8x*=5@uC(il9jG~14&N_ep{Qz=%$49K=!Zcwa*6i4vt$RhIFdNJ8LE z%24A_)7$Y=b?)VE3+Jly(?pS?49vWqPdemEhIELwv~*i$ih8k6u>1e%#+{VU2AST> zYz}4y17G>EF-z(iFv>H)0>A)u{B7T^WNYXZd+lVcrj{`0oG%!ZJ-cda`0MQN0)@Pm zK0;Q^dH0AK2j|U{3NKOW(C#R*a6E-2HQ)=Uh1RWyP2g* zV~soskw)jLDfgF&86eSuU<9laEMO?%LU*qrV}I~J=+~fJ@OWF^QtD@X#-G&7#IZ8A zZ4R&nIByV_U~(!x@>FN3MQp$;-CfkGy(&G#b=0X49{nTLnJnLC^TUc(&>uSmv*;oEqA%AVP`W{p4oAi2U$AiAtdI%+SRIK?GoI9=T)MoYEW0AKO!qikdcmbp;qj35 zcWe0l+JuCxw}Qh%LI#q%&#=mS+>{VI8dZL`F4b7h!4etowpOJqCuPA&D=&w8LI22! z8yn8UDu;=*{;Wm$FNssNeorn!k~fqlZ7><_X0uJLvbCGng&Zws{6|IgF8Csi?=f>C z(-XlknAr>SK%r3(~6#<>73m6;%Wh+z&)E<1;=$2;ve%()Xf8<=l=E950b^uL;4B#ttS-lt0FQB-G1UAM&xBf-P<=M)zkXnVNj z?jbTXas&YxgrgOzOYNId^hVm4{1WUM)4x=KY@c+HZxz){Ud^9a^W-pi`QGgvXajnZ z;Z8-DDHyC*V1D^OjeFT0IB(;2e*p|{a3>wtj5L>NN8k~lTkuS2+OrxOxl%U;9_@N< zd;zyY-BdMqQ<^GE2A9d9K(@s*0K?9mM5B|_Ii5A`(O7{ z>I2C1P~rJ~Yj&ezrzXDmLDBgYg3NM;g}N4k6a|hhUdq$Sy5L55a63!=I*3wo6z=uw z!8>slzrVGl3PU9TKsord8D!UCs<=%c)B%{QT!BN#)<%>z zHcr~{Z_PH*0l(sJbakAoc2lGjjIH=?4arw2-}3Z<*|1}TYpy1EEJ z6GYyODM(IEIbAn5w}%gzRGmI-2W+LS}VrFm($zJ%Z>W)rb|d$Q4v-o zGIsamEGQ@lbbTlf$yn9p7_!Tr2%0>8{FpnV4JHX_Gg^H@u|+_NG4T6reH-L%BN|Zu z^Byc^6!LDPMe;CqD3h@@Pa7MXp`oEL`fuRW0DT3e{?O=)LnG)f?6LK}JJy{GN#Fqj z`xnS23`%1~@^S3!>=R(@vVZcV*Xgb7iU1_R7Co>&pQbU z2?^;S8sS9Xse=d zr(*k7chzO#ap9N~VYYDdx~ADpm>K+33Gh_vrk$e7%ZrdTc>-xv>ioHuMu&+wKJk-D z&)rv$z@mZM0>pa=L{hb01^fO%Ha5%M;karnyKcvMVL4*4J^&aqPQ)WKWpH<{w z1gXV*M>f5eM&#+eN{rvgXEAySVy-UxTITE5+?Amn3$V%2b2DI{t_l; zFLx|aM-4sXrQ}B-(@8#-TMB|xVge=^jy$XG)HB>jn)TbO2mXx&1PE0dVCYuX!4-Dz z-jJ_{vikFNWiI?UW7H$c>tlvv{IyEwfQhXtM#yvf@n=+p$cFBS=qRM!NmV##1SD9) zz|T67iR34}cpX_Vi&w{#H8QFz=0)Z> zGW8kNuZSZC9G!hp@x;ZI02zgT63T9T(~cwr@?iBTv2N9Qplq=S2=P2)+3`1X$yV}t z`QP>Vx@q`YO&#~UCd=U1AU~vR4`X9kOVsH2)3zVb#A3zZd@7aI`twm_AwT9_LH1*g z-7p8IqJoa66rMj3U)j3zGc~&L!Fbo_L*T9Kn*1y7(||9j4sW}b2A`({PE=9gA_Zq9 zORCb6{&Qxw=c_-qm=KultPwW5P&KKT2Wsi|tlCzgFswn^s!s!-w7e#f(Vg53>$ zi*J3^qfirx%{rm^9S4UdWyQ}cNI;9^1an3c*l+)vKZ_}$MC-{Mbu3u?=*31pH#H4z z3MW;w{ltmH`s=X3mS%$WTpc_ExNK?HKKdkwvE5G{NT!Txzc@b`wL`>*q3CgVR)Hhu zCF0=XniNKL6ISiy%48jY9}JAi013hKQi%he&3y0`g9iv;BN|Owq8b*yA4y^U_3e|K zZp%MMfzLqcZ!z5h;115?n{ry5?CjeAyOFm~X2H*4tRLyF0+q!XeCSI3_b9M}UKg?h zvG%2NMRXF24WT~M9w4MPPMf62l?-Od$!de`udgvHd!b+AE94ljrgd9_AKC$TO>s<- z=j^YFViK)QoFT`<#s=>ae8u8X);wS9;XHA{9LPbHar~ha2<6FYLK!grVonz&&z3d% zB}5hcQB9w35NnfgVHSCXQQ%{n#3__n9FgAoo;@`cIPve6PM5F}Yf*vj85q?5ZqDL1 zf~(@OkI&!T<-tpPw7&lJFhcj_>sVKR(of=*fPrdQ;^cRsZ1gjLJ>e>31eAr7jiYwI zH#dhK-G^@|-y1KbSzzBwte*45P~Y$l))P7;2;yF zk?eXWYHf2=0{32#- zg13ny;qey;6>v2=pKsOwllYnV+9Oersq@zOPloFE-KJgZrgGHF3$GjU zQtlrGu5KLoih+LfN~EB+b_kqdp#YQntnZB+FYPoRm-)8O1Ap-7K8UXa`Dec$!=4}p zFF=7ZqI_HK6=ZXw>lvIz+S=M-GW=>_m+}=A`_pyp;NZY+XrR7ka5)y7N2o^w=(})( z6)$+z#^EP*KMqzXE?wZO)>bMl*IG>^WK}o`7UO+QA`F6pLRR*2OSCo{OXH91P4S<5 zN6Cpedie$9-!L~NP2o~c?U?3^_4$i2lBCKeIq^5z6{ZVV)`6b~3%ZHkRo7JCtrb5{s_v2IBc zih#E1xT)ks<5mrni=!{bv;52(P zWNY%qE~VF6L@`ShGJ1a!osTdzI6!z2I5J!?LXZtTTTB)0M_|I|Ud9IThsT8&s)#-YJ4-3bHPLWb%# zLV^O(ydtd5)K`=|(;BlR?D!~0ZHTD*yU{ygqfas43UoX0$;^pm2#1Zd-LUwq=gOR4 zbgdE99)>X{ZO|j6Ew9(ieoI))6nWy!OjC4PX{@Db$+}l)G@W8SlzdiQx1XOflkX_eJlA5l_mX z>F=uc*qpwr7$yVlTro*2bbng_``Rb@l5^Y$WaeWaOnA0z8jO5Zs_;Yc$v%}HG5n-& zf2!Hh;`vWgFeh^ACBUbTsc`f1J&F5#IfEiV{3_*HJocbmHYWc^uB9f72| zd>Z{;uIv~q%Dz{hGN zjM>+(&1H|p@|u-KO?;@*+@v{n>+oh*BGoLIxv6YV@J*I*k4;yC_6xN`@9jt_ly1JJ zY-OX)2;VInaSgJM%kIzzWL9NP5)nEgML0KB^yb-%3X1&?tue}z*GjqdZVLSH%TsLI z8EG8BpZy@nJ!o{BWBo3KC(@WMZgE3PhwRahZ{BJ>u~z0Mvz<@@R+67eKCSi6D5ak4j#~y>7 zx0k{Kdx6bYH+g#>yi)#42|_jc9J{$;QrUycd0_69yf_+$e3i)y$3k_x=HBwoyl#dN zKBW!#{o26X4=1pEeQ0;!(JrWIOMtWjCFuU0PY{n?&R2k9*^uYWNBUvsx{$MK*f9AW za<*a7xzqQbd7S~5SD58q8=$sapLsS8j%$QGRo40H8<(xK;C@B;^KG; z6w?k6;VGlEbV8-Rrzq0;ZMXmJOQO>l=O;x$J4;tr*X5-EkAWCpA@nO+rVz|eu-hXb z#N{47ECxIV4pKlM#{eja<9bO6A`R2U!42l+C9wWHx$yhHoh93bJNv5pOHTuFNA1$A zGQszytEYE0a{tq>UjSrJM-}Owr>8UKRDf+awrs7huMhk}2zOy>$|#5tSwp|z0QwD< zW>IieiP282d9td%&}`GAAyx!~>*l6Fmg;4{Pep|k!mx-e23uYWs95bbJu_~D`&mSh zG77~zV0VU)X-9lDNIVNv_-XLIs^iHpRvdv5<7K42QsjA0$-OMK$M>+}JQXtuq8KGQ z(uY?zwzoCP$HAEk)cEr9@=9Pi4h}-Tbljc@ok+( z@HZ6|cxBwY$zZ(u%=mbF+FU6e73*aG#O--Ymo$O7CNI=#hy;bnOE?7vv6?oGTv0s; z^4McPe*Dm7$H8ce=5lj$E9fdIDe35t8P{OI&d+ld`i^mF>kBN`?x}|64=7#o8fAq!?5)!7=D5D=~Y2B(=BqZmosH;=7 zzODKA^NU;ZJLf0cPzRw>7?tX4YHAu9Qdf=ww%eQP>q*TB1Ir=%#TI=7z~tQYpB6$M z(8QZJZ=MzX1I9WnEzOR)MDzMdC@w8@oH#T&z&Jr$Q4ta>>+~O5;0@@#1;l1N~AAqN=nRC9xv%S_v zZ|rUv;w;+o5k0JY#h>d0Z1{QZUUjAi78~9SObW1^6H;*VQxc)#Z79LVcQ{fg3xl&d~Y+ zNRI>z1>mq^e$j(J$q?TCRECOxv+|DupT(OJB*7`gkvVi``Q|;QR49JTND!YAuFW~< zKwVOhNDy|V57iibOW{QNNfAj{>8ElTO7)yoZ?gbxW&IRA)v$qbW+kkUG8XI;RO2u0 z7nbp=9CO*}<9f=spO++mje>qgFHlp8)I2#RvoY?=?k!PZsiiNm2lSEK!y%7mj;@aZ z)$bN+F@o*^Dtekz(hjACc%@@5M{r1FMj2^-)&5&mcvLcjJNiZJP&YC={1!-YyoV|HG2Yxce#A7_CmXz1YO*7mnb}OWgB}K4P zw;(#)A&P=FtpwgfNpt{veiRBnkwOx6`>z$$!muc=5D z&)dw=G>>=F^dpB8!ztxu=Hs!9*g> z1MAn65l>~zN5HrQU9o}XuZ$p1m#SoJeQV)+t{h59$nYoZrBOp!VIiJ!oE#rR@7=99u}`@3AG9~or}~Ml^CJ=) zRkI7edkNqW;tiAlwH4f6jfR|Hd0V14YjIx&m#~VmGRWJcDBPk#^*Oph?cof*xq(k@ zN;_0_Ery#opm9F-*`7Dk(z<$1SAg}p%f;vHaQQZm5A~++UyM(=sbFcB`O^wZUOeJ4 zXtPL=ni(B^ZQUCSZJJ$E#72Bl`kUL^7H}!x{Gq)xc{`2%nI-Snv9aB69>X-Ep3m$Y z9S4`)0V6p#nbcm|cr+P-2Q=5#E)vYB=bW9N|LkUJU;w?L3Jlegu@z-yWgt~EOUmr_ zfIbJpbz&khFvEgYJ$->Fhbr%6=#o}KLt+t51jS3ogqnspy4#YHlJdi&`?a;T4&WvO z>Gk~l9GubLzL^4XXM63t{MPn16hhENvz~h{n8Wp483*fX;u63#BSXVyQC6!VuC*^; zatlEF4K&OMQ~wfv`TF%O&FN{blXv=+WcR#!O}O`H3wU?LY1rZzf7?Jr2e?m_ z0DYN|pOb|ajUfXt|9*6TdTOfNJDKW5P4ojhw)RiDqTNc$nbj00!WrbmUj|);U#PMG zR4ETEH(#ohE&n(;=(yn!nLNUDP(WP*O_1~J@0WH^%tda=(6CF+B4%c02FCgHpOyzv zxmS6m7?sm)Iu{luCvnz=Ke z$bCpg(6Cj(270V|Ipls{X)RRwc<5Cjs)XQ+lLa0go+)2xol0F`!&eKVp`0@g#`Y2h zQ&x(eEI2XpR=NpeWr#3o>FF|5=#Zm~5U|Zc-M~zmVsq4uO9%GoJ5kV(7A^Nf@^~~U zP$EAHOd8nIfq&Z;fLnH0um@(5nH2cOAYjK;lvUVLEX3(0zLiZJQJsD!!v?|$=ibZJ zsppA|Mf!7jAr|K4}dlLWpoX50Wi$2RMr10W>?X)6R_1ol~tV=A7USwJhJa)p=c?X+DUKVva zyroB&Uq1Q1Q&v89->P^$#!$I`#3Gw8f9g?74|*jAq_Z`Ge$&%@WD$)I-$zF#=k5kp z7IG#&6XkjMQxnne=F)!f1>|X-pUoA|xWw-MT{JVe{kv>{n&+O$th3ST*o6t(nqa2n z*LFv6Bihy9i&yh}=Ex@ZcABPR!C2_-o!_dSHe(0>-HW{V)CZw9OKA87^xw)LasSc; zx-12#m+!@SC$q=O+<5*J@rp+ro2-wpAWFH`?=`aG-r|mLhd_u#{*ZGl+r{YJnOZut zZIMrL$LG3kI@<{sTJb@{iaXc`yVZ57nY<8sJ<)q-rUZrMxcXEo8@xtn#`dBEeWFx_ z8DDB0UYt~1w%)zoQePUbF;50uB{X#Xli>Y33ucYv627~o>Yl1RFR(g>R;Ag>&0abU zp^?@7?^)U5XDF}1s~I$s#<1e{{S9{`4upWeUF;m#Eo_w`+v5ZF5I~7Lg~e(Rkq+_#2d;k5+I;sGltW1O9>6C zyX0!Xz+T%+bg^sb$i4axgJGQ6<Eq=ETWV<|adbv}wL`#`apr za+>JrnJe&Hhc@tlyF2&uBe8Rv1ieD25SrS=Xsd_P9(q{wQ=IoBu-0(nEw8CMV~BF7KwK=V7%ZTS>q4Gh~n25D{}|n3dd{rN?8WCdSP6nR1s0 z=l=0?CXgoSmg=%?Bo$U)_YCJ|iu!F>W4_J9t}UVDbZVpKNOb*L{@@)omN0sEYM+=v zju$3pM}aJDM*(NQXkzwD>V0mszCn&F`j`K#FLjyRc*%Qfh$a^l9icf-vcYRwH3#~zgS4;vcC0o&!2PXu<^!6%zd(4)^e2uWHco%4=@#vQ-@dJ$@oL?$-qzHJrH zlivQ2oYVsx-N^|iR7#jqHPD)@dB3<qDLrCkMhGQquAgQv|9n&G97cbVK8bzl_VoA)1wP^* zai5hCGh#s3T)b75G;_kWrDf^^R{GXUT$E6Y)yhwBYCyptkG{x~rL8nT0xuuMC;5xY zf0}{EUiTexot=o69bam^MMXv{CNg$KI1Njf5WHp-4CzD zOLf}97MdI~iRkOz{u!1%%v?U8(I0&=e$#~2{p(s0Xe!QQ%l%+~%5q}0J5pu#$*W?q z67Lwni5ke9%kf8qU)ts=)r~nhik=_IPEm37RHRBX$~A^D^aev)_L+3K?s; zVA$rsCcmVSjd1YV+M}OBB{G@*_i}!Jz`bbWyJQt_Jnu9>Iu1>V_$n7`sZYe_{8rDtKx`w+@lui4KMHea^KymTK@Uz1j7mcE=!6}LraGC+eF z^qS=ELtdS{2~;2!wx82k0kRMtW}V47jJJVw&7<2L*o^V((!E(>x1N~rU|!9^DPPS< z6#Q{}q)B|0BmU57!+x9cnT|&FE9-4mpM4vJv2Ez(#G(GjmAl}JXv^Kk`5ze-m*U?G z{nvUVHJ$8Jei~oJx2EQfzVWKwMYq4&{E~ChKYCmKQs=d~MA#R6d>pTk?zy}0J-^rR z7oh*7qi1E`ItNRZ#4@-)&#QU;6lpi*G^tW|(Xl-F6zg?@eRWxRu$1n0bK@IWg&=-B zp4latom({MW3U)Mq2?2m)}G}irw6nU3|2(uR)CkY?sDhY3-#TKUgqcS>uivEq#FpM^6Gps-B>lU8 zYjo+0QumaZ&qff-y{D)tk1(!`0`fm$PI4t|pvT$7RS(n;MHE=L5LZl)P|iV8stEt) z_uZv`21%2K%ZW|;xBd+xCu!+3NXK70PXsPEfpr-fSXGStcHfg|Ln_*+(#6R8kz(HgJrdiU<*)jns&aY?7JItP zO%tV6jzrEwq5mv2V4_b8UwFZay5YLK=dfLz9Tk8D+!p>xzXrZ$gvC%JL9+DU&1Gbg z^W$1%qagTx&gV$|%%A%Ck^(>zoe>(5ebFN(OIF-9_~6Bh8rlMFsvj37d5aAdNQ)|r zCe~~U7>2r#K$=5-Dysj4!(EjXKgO6BVARrYo0iJZ4gRwvkvF9Sy%ViW`P|&}G-tX6 z-|(-> z4`s%9IGx8QqYCswiE^{XM@;%`K3O;ty_AffVgg=kebo#I zdkuN^x-R38D>5`wzX^_>+9vKNP*H@z#FkLOe$|bt+kqIQUj9Y6#SnIQ4lx9onl-b4 zQb|Na#AmajVrV|4C$&dRqX^2qN7dvaE8)cCrOfKn_NZI(q`Y5>+#9kNT}{QJRcDvM;Dx zJv9cL5g!T%U?8{O_}jg12KdH`*NY&;0e~@7AW1uOymtT`eG8s#2aoC#_ArqNu7new)P2L4+4UlpIaRJ)YwpdANh_1(3Pb`DF zR_W|%w})1a+4gWEh(To677zs70pjWY>PY+Ktjw)I(|K^##?WhTGM7!*w~Rojvat&?Jc+Pm%fv#ZY?S<2C-M7qQCXGia=qo zWOd^rkVSjE(v}_w&b!aqSy{W;Y=q(S z^Yj1L+~vGzo6k)vPsiPYi;60z6Klf186Tb;%F^JscOmX?eqw4-XHc zruHKtAyG%uVIWp8Y6JGiklC`uKc~PTJ}r%uf;cELGT(m{4WSF9n%OxxBH7}psR>a7 z^jU*gjZR+}C39h5dQOyXbGrRp8prmo?z;ltd^0f#*v^uxa45EE_+n;eHk|Gn86(fB z683lFEGI87pa2deQbBS?GY$uy9H2OYf~ll3_!}`X*jgM>RaJorvRv}A^72fyv>Cl} zL;Sd1K(f)kbFpMUCRhUx4U(U>s#n&E(6(*?jQ=S4CtqXDrX^hvc1DChZrCB!dY>j^ z5N0;%2H<5K=cU+Rzc7)@vY;`@$$2fZeVC-;n$V%4Av)uN5tC4T=28$bV`NMba6Jc^ z0#!}sU*o56zav=)QyPDFgiO7A(q4;wLq#bR<2@|UWclC}J-}(R+72KD2_@iU!3EKm z(ck9P^WYueiAWZ3WiOR>5*P;z7ZWoxu7oYnP-POHUD7!3O;YjmCmYWL`4A9J*q}p; zX8?bt&%XcZdN5oj%phD%VM4UrlCU`Rc(xRz`r~LrKx)*+_nGV5XW{7j@zDbi%(m+v z-42GU4gu{%`%2Q)_0Dll2X87Uev%qd%)!AyS2qn5E2J(enyg}+LN!7zeH;lhW@ApmJrmDm*Bx@}r|P@OMP%#xzooW$6k;~5)&C}v zg!C@!P%*ZA=XuFbA_*&E#5huVu?Ue=#6yKgZ?Swk+N&%;wF*!qF}7HS^)~=>7;2Yk z2lDL?sZQ@qZ`*-5uGY~Z@5o%8OKnR{nW^1%I`O~YWodFe=(Sx05&!^F;$~`)FU;5! zDyo};z-xg7PNxoCEShTGDfWX+q446QZwFZ^wTzDMDf6JCtm|uvM%qBJP-qNUT3lpg zLTD8^nO~A@?c^?dlI%cLGw5HGvL!X;dOM32%o-MEMYdKiYhkevX#-M{j;reb)d{7f z#)l3vbhZc&zMou2rb8plMHTcc={UHmqm_tE!+32L3guC>Vz=?e=zj}?ULkdNtQC)Z z^4mC3O&iT}^Ov)bUrlQ#ZWB;4h32Ojg+j;Os4w5;X4a-DyW2r&@O*K}CFajQDgi;B zyN>)7ZvFMDQqNZ*9!5)UN%L@FPwk1Gn;uQl!H-$(o_90q@w#qiE}J_98V0veZ30DA zoGdBEKnR(lHgbw@g_1~)a5P^mgGEbm?3*x|HQ`q`pR;Z@gBTRhCEp-H=@URL|Mw(w zcI~*?D1JKu+v^qg?n5%~kb@~Fye}NWQ|BjN4;}dGt^R!gnxg1|X^Pha!OYJ*>MpQw zhJt!*a|wT6adNFPDvgMX$)VBQql$aA;bc72V4K}{HQ9>MYGP9@S&yQ~WR6BDkTOd! z{Ns$zs)-oMr6rbtnM5p{bEJpm=l345|u%OT&qcY(l(1l zGT&vye-VjEcW)NiMAqH_a%n*rU$OP;Sxdrtl4wPCJnT+HcF*S+e6enOK2Yz&p z9h{aS(&hh)-P3{FT}RAAkin5eSq$_YCO=mQVFGD0^QZBDnCWAmWb--ayn%U-z*HQ% zb}fxqf*koRr^7eqCtyD$PNybew3QjPaDaIds>P~R%;V?tKg6zOtrm_1l4xp-dlNtWw%WTExa!050)tOSelRc_UI}Mp#NH_T9IqukQ^BA16LyjEv2X zILaW2_}rHq(-aPjmSUG(W0NPML{!6u6~I6O9A~w|m#(jjQkq;Nwkp`IMYj=EeoBm< zG>azJOYe8i28@)MpxfXt=}=8SeH!GbNtnGU4nja)IRf7I2`0P?%A*ViH8%~vRfJ&- z2@Z_$o-Bio&lpyY)3b9*#`SgI<2Q1&z)zy`~W3*x%=-zpp zb^uo;5XKD;=Rl{=eo^iLns@n+t&ydrB}kmn9%=9-?}EZC7&7?cI?q-Rt^M<*3^c(P zP#IKI1YTzDd6Q>r0)d#5J?y3iGo)9 z6Nr`gc3_|vL(>-jdkrqOM?$hFsi^!DWW{5*xGnzb*J_D->9`(9{`sFTCk?3CK_=LlMps2HpisXjI#$RV0Lz~QU8Ij zS*NI5H{oEEt=Erm^E9EHXlDS6)80m*L9r+biA87!4KSY8ALLu;t%#C=M} zhWIA?(}kD6+2E@uR>V+f0H^?7QVAu{Y<`M(<9%IJgiv$RZ+Ifo=>$#RR$A9*+4N zjp#z`Koz}2Lcrzr;GP3DC1uz@L z86@3_baSN3=WYNH4@%BJ17(8CaWIv6mPVnH#5T*ys${}(f$%>)H~^3 ztw4X(dg&cUK;LrZf_@(067-Flq-}pPn)EbqGraX}S0Qc#6l%Gb0 zUk9sdz_ZXnU-`t^=pC2ZM_maZTLY9u;rBtMjf=Zo5|!>R)9&V#V3s5NO3Ezg<3&S( zQ0-%ez-O#J#XJNmS}exrLTrD&uAOz)XK*{GX>6L70F7#4`;d_~i&R?SNH^&Bumjnf zAIHJGBiMT~egqx;)Vr4AuYZkAmj3JL|D-lK1C9htM;)2a-ILT@#Gs{g1?z8#5($G6 z#Qso{bqL3s2UBsy^n2O~lzB;n%s5zL4F2e}ErS^6=BipbmxrO+k8>A`pf5d17csZh zeXl#odL{U$gBk<1H&Xb?0jv!KS(+7jaR|1)HIk>5YtZrird9Se+0Wi{A)`q_EeNJS zx?n}@Gt2*pkYB5g`*2wO?Jj#(#WB0j{;ELf=hcCZ9&>|h`)#<=1XMem=1oMGtGkS)rd-TX%SP|GO<9o}c;dJ0Icvf~`MEO^r z{Z~UQsF!s@_!-~+G{Xsj7i%0SWJj0F@yPd$;#5_sKJnJMtab>@?Dik;|HbjV;aeLD}V$kEjKQ=MZF2Gn)xp}6vCoOB@@>T_l{c#Zr$oGxBHS7w^IT`LC# z5fy8{&GpXK-q*{e?4G$``>k3$N%URN8NA>Q8w6mf@Maj0tX+C5Y>UwNKn##rvL^5a zssmuaQp^okNIzoZKiRQ#G_~X2q{P>=Q%8TI)MwE`hO3l079!_(o$;>m+THtG{GXzz zp&xaCi;->t{$&b{&2G<0Sie1t`k`T0X|M3?V{Ll36lMcaWo2cbJ(?puPI2jOl=jVJ z>gSyVHY0BgmUY8;QiD4m%v5#hqSeb!C(JE}jefghuc=|$!LS)g%xecs6P*o{`{=^f zpZDMpU!~&QcMk}lw5KKEIRxlkYN{uK%4!cV2!ik_ocBAK#cXbs8z~LEZ;a@)0UqD# zvuA#IlE!_Y?1ex87W1D?@ zwws{vCN>I80`%PWa=|BG&@bG|QwQkgkUxTwN}U{Kg-+6*cz z5SLZ{!H)4WIF&p;f~YX=Z~~jJ_*Zd9{r-LNrSqk$;#)B6DU>K@PH>`oA7e8CFE3flr4H&^hS>4d28B@xBPK~q ze*q2Rl&?|V_RI(Q(zZ{l4-cU?;NFR3y!@i^VXLut<4T6L!e*@#pe!l>h;S@B&fl?( zy^m0bv(oA9ZoAj*qL3QfT)NOSFxuJydZJ}l2m}>f6g^&|?}?Stan0w|dYW%yV&V>2 z-s$TRy+2a#qLJXS+mg9V9?M@+>QqWpi`C>)YN{6A*7y$am<^4QJbxaG1)drlWbGG2R7ds*2B#rGuOH))bqk8XG=Hhm)KHBd z%BaOuMPCr(R)b=M%nF^+-+#giI6Gdj$H}|jkVI4>!2NG|HWeMg0eK!^ww`x($kkP5OfV+>g0_pVe=exLUs6+ikSJ~VilZY&KZ_aVALKB@#ymtOl9(w+ZaB``@ehh31KpRP-r}NN*JoJ8=LEo z=!_<=IOgFT{3p-iu_1$4l$YQV46qRW5SmK(YER^;ZTT;z@ds)-9NF`}*pGAh{6WDi z5pf=cJ>D{nr-z%@-dnj=!$Sf@h((Jy(v@r!4`A(9U(Z9Z;A1Ro@Hu}#R37T*rp3V`Q$#p%Z1e5D zn~9s&SUQye*q4W@mO6;<3a5)J(+2%oJLR8uB;^cx28+%3gWAz){KatYjX`&KXj%=#w~ewI)azm1p) zC_($CQVB$YOrI1tXMT@-!lb}C-pqIVD9&${9|g{^miK!Xfhr@2__8M!`N$N$>q$f= z$T7DIi0wPV?cl4GLt85y(IgrzFtl9!^UG-di8&b4snD2?2WWh!f2b4t0=~cqn_yR7S8G@ zYHJvPvtXIpLjV6Ac-;W&wminx(deDHy?JOF&&!L^G41UbBxvmF^$iYOAhZO- z=Ss7ce+C+cLFY)bl3-z>Jyj+DkGFq%9O1vOlJT%}`XT-~g3{Qb8WGHs0rhPWuaqsH zK-lf4oG4-c0X#zT9`pWzUz`ELGWK?;{e8fH|9uBt zQD5^yRCF{k3gqYmxHGI=*ay(hY_3oLoyGzVLrnC=aO;9D+iMKdtt0;bxpnYgRCiV2 z(EhtAiGM3WV0PoTN?B^%4-G20lPi zMRQ7)F@&$NedcyC?fPN=zI}xI$jG&xTK>H;E&r_U$2{i$FgWr4KF;+ z=?>~9X9YTD%Uy(eOMdl4LeQ3u{#L?4;;?6W2xMRs)g0@;M>{~kg-ivs>-?+3P!`X~ zlfv`b@8Tarm<^aJ)>z}^g(ZraHsa+y!ox!|GFSAiatWao>HPQKg}VLEz36u|q^4!< z@TRZ}s}$VX6tT}kaL_8^uHof$BgA3Ao;Wqm%=bN__1)5Hd)iAw8jrBITTL-B2hYHf zk)|&{sfW)`Dwk$!-_J>I10C{94(Ot5+Dm^zvUt2%A^BCr6kCsG z=YmZmb}<_bjVbN{FZJ%PD`mGl5&qEuGjDwmITbzGW35;%0cXM#B7w>1-`;%Kh;5G~ zmXjlN7w{FUW+jE#8!<0{46#x@B|>u)uyj*d8k{uw>}2W)iUCH#l1YrlIlB!`Dba8? ze2Iif%|q3`wQDuBxFZ9F=;lHEMdlU_{4+sJY!qF#DU{MjA6idRzL|r@0p7H@c(=F1 z4TJ{!uyMz@%l+$zhd{Br=&92=)jK`@HSYg^kgK^~p^iF=2=YF(HxD0@QBk3oKUJ#@ zcfk2yXIULsyEe@LzK^cFD2}VVAK|e|`h1VZFm?d$eKb|Rl1x<;9Bpsz zhBKGH6qn(?y7p;uYHDXBdFRS4G&JJUL~>AxSH<|B?W2Ch)BRzhL}b36x=&=Bb3n)FV0p)ZY)8h`|<&|Ez6zz`;OkJmx5AOZ2 z>#cAsHsh+3zU*SSpK(ISLWwmg#Uo|N%-Z5zWR`=I393Guh3SqzN6n3Q1)%LV&om=%(%L0I9L>Yihp{kt$-+QMcM)6-DijE!ut)P0^ljmjO!_#ehu_IMM^b z8-juDCMy)9{%I8Y8ba=$|L^w~W(KYzNlr*RK2|Iwb6tu)Itacm2Lq8#K&c=`>?PZl zxx1$l;;lg$EA`PThQeE8HTw3n&12i51R+{1PWbV(>+U7Jgy6xVC=~U!?wUxaWs50T}OB-;L@JOqMJmM@ZU#Q%j?=XCm z9C&%e^}xR#uJ7LG+`8zlh||OmEVulEdSK_D6vq!4ufVi*6?kyA{5cqBs4y-0TW7IO zgOsaQtwQl|%^e-GcG+?TO*|A{jclj)!xo*>M8%bP7Jrs1{O_ai7~^~beZhdeJ>^(`!A91FNS%vCdfYzMZllwVySoe zLM`(&YUo0sT3Y6;_<$f)aDE?|h2yeyA}gS9!UF##-D>0nyG2{g6;=b_n#%i11I3(4mVjX9 z;WZSYpUsFp@gE?L+hJ$;$Hbnx6kJSpEM8~!lD`|rlFjd9sk8bqK{-dihasDpMiZkr&4g{qM3^S#YY##nBb^BNh8;_b2%QoZVJ*l0 zr#(IfI6w4$ds?=TdVKsiV-Har7;o6i)H8f&_+MtjlAj82eh|)&oUjl#1gP9!W-^#4 zZ7M_vB1U)tBHD%9)TP@mV+_K4iRe9dP*TZ5FMq)^Xtj7%>hOlWi zBc(m)zaet!r6NkSE^|hN<+rZA=s+CWwY6`IA{LAHMNGUBD z1Q&&|pHbu$P%1r{F6KTkMy_FklU;Op>@93*13Crh^ zvnt8fFYjC)D-J&5Y{@Osg54kj45$^-z|anYMYyO4AQ2f;vU4m|UqTR9XQDACy4~T9 zLjCmH+1Q_3_kEaj!AqzvCG0A%BsOn^Q4)Woq@XoBqClU>lqCsFg#bec@zfV&P-3aH zNl@oclZWT(+ke40HH{6ooHw}y<686~eqU*7FASj(weIeVPdgID7-uBASbK0YtsP^p zJi>dxapn)Ho;VeEkeSsLR+AS7rH4h4Rry4GV>P#;G$3Kw)rvA#(F|`y$$UY7I@#kx z);0rM4D{m50&sbf$hw)$bUef1y_a~E8T9p3#jZzN2l@nhp3@ghHT0DpB;)c8=^X)D(Vv9n=hwV_-bav7%&ie zN4xLN?%XHE^u=eH6%A1c=CSAQb=KPcZ6YwJ;}tIO_)%Y|tp(ryRa)m|#QfN#;sfJ> zYZS;A|8EcZujRufg6SbFJZfjhTeE{-?Gc7Im;@LQLrg*BN(8&ys*ND)PZAkYaB@v5 zun>Vxurcal5Io02T2z@1&(!-K{D2b{ecWdbqWG`|1lm@2#;$L ze>$ThvnQ}oY^YseHYb1?i5!JoSHxil)zJ*T0L@EIrd>+}ZrXwwR-;=>F*`r=tq>lJ z4jxJRqJDE5*O6t@JZTKQX(s*6j1g@NQv!^$yVtYwZd++b+I8W_wq1`m z+hAWcQH)M6tv`mfHa23R$xoH>KYTQfDkl(OvS51As%zNw8FWe+g>nRGW@idCm|U8| zLuQ&o>Enuui~_^t!{t+}UC?_IdP=$uHEvHL%Ur&?)NtLW25v`qf|}C;VTK})3xRZy z<;20^0y`!EJPqtq@*CrAx!cRq2qymAO^>diq5kf|`_wvCe4Ue~HfV`xpmQ3j!hwKO;Xs`V!JB6?QUBBj zQ*Pms$x~Wzu+jKOfTVt+?mnAJ&%=s6nJ#IiT(1qlU`FU&D@WK|u&ZxwR}zKl#l-@Z zBK6=PYSE61xf_rUl=QI)q(%-H?vxZfFzFTooS+Z^x78ObD=R=AfG%tCmzAbA=GEH7 zG}kM*{Wa70-5+Y1qZExL>!YoX2BMo!&>W@xlGISY#MhYi27XMELzZJpjn}n~HRa89k9(16CIoRt zykV!BMH*}WdCliIflKAiqZL#$|F_Rlmti>gaja@A{;-|Ao@Pxtye3hq@ECz_WZU>8 zQbomIuwQiiS@`W?X6zi%8*72fdC8`((bF)6YZ&kLj53pM$SvI0^mM-sWa7Yw1ko8X zGVwU=sZQ%Hjp-Rbd`P+;KZYsn#58XdkCX(MR6`Q_ns{xR1fwp|{#?L&^li z>;=(iE&>k=}sFbP|36m-NdBlkLl;tZRT=U=CQtPgbkhdNP+R(>#<7|Au~#ymJX_j3=0`zb?eB$ z93eU-bnG*{qmsCPS1n*@Wb|z~LDY5vRFd-AZ?htf9Fx>rCX}8W&o#FlFQ;%DD(}_y z)CyuiN$$^SjdIO5nn|q<4Gn|5C3>W~$m&h`A4TzC6frSS?Ks3!FhoRHDMo~qU^5Iq zvO=}_yb)p1L@$W=`SA1=lOK}cSuy-XrnLT|tH67USboTmLDc`K^*9g?9f{Axgbfmb zdz_n|H*}3&{yLfL;FAW)Y*+2p(_izdP-1xtDZpz0dpWGBqwSbq7!}neZny3c3uSsJ zdKe5!O?=E*PwH-|b<1uosR_$lxKq`rmi-{F|&3oJ{!M6UlR#+&(Nxhs1&%$A^!=b0Low@({9! z2^9){tPwY`>8YAVre9s(g9QuZStwVLlqnR5VDv*Oq@`c$C~AM5s`qQjy|hP9Ahmz!1X}gE5A}Q1tF=nQc4SujE5Y!y*W34OtI` zP~xYWVlt`eQ*9Uw=qr5)3HP-7G3{(ZzEurj30oaL;mJ}r@hswyO;HiXH2iOJNCqYX z2jN5kCLwtbTT02`IA_LVLQI|ns*4@8rdYJ7u)|RR`kUf?-YdE|0~}R<;oneWKkYcE zYOKELvV^zALVd6yve$wJk9rcNY;;-b;h8&qYV3%J1iL}EpZo(i3EgN!MU1gT>(#lo z*Zh-gE&~+#y#V-|W7F(b+?M2SN{&=?Sz3+ZK?uFNsvmZr9hRh=My;i)frNpdiX!^w z7K8n{gEp(QJZhWH?qX`smgj^24pb3Af3_dK9GhjdbNzw|rC?choY8sr^k*FFRPvAt zhVFFC_T|y#s&9lN-l6%p?3Z&3K<-D1A716OcNx9Y{bQcVvin_VPFV8!{nIvQXvbtL zHVVvdW7o5>&Kd~9La{eB*UUFKUDB#8~=v+?K>}F5Z<B zY3oL_UHOxzQ{B{hW-;mgmMdqyA2)r%o+`T9q~yk2TGkDZarLb~qC4dLR8s4$&TFIb z%CyG6T*yuOK*f!y_&#sZ29WY ze~FoR!MUJ_-hdYK{^pUuahb2Bz-ud1-F&Q7<*?4&i(xjM+iUz~y@iE$7#-qr^mC4n z+8H^r!qS3UyYz4}$z0?v{&N!&V)oAHJ`qisJc{qWLXOwTIeb#wDNAPo$4Ki_u>6D9QOY6}6}9nES?v4qGRBXv?y8B*At= zWV)qJy@%&Yyn+|k{@M4bjj8;|M9+-X)qWUt;o4PUKa8e<58uvHm|lCK_)}hv9zN|h ze-cAygw4UMm6eH)7)b5a;XBT(C*>pmy8lI)5k3Pm9K#3G%3qfIqvxE<-@E0M`|axz zk~b}a;XM}3?>#55LXA~tIm02rmA#An`>hvzsp=X^tXBbr7oG|A>-Afgz7e%O%N+a5 z&pHS(v)IDy$LZxPt{e5f+K~1Pjzy0BG;3~;?;P_oMY1M8&L4%#o&(#FMx0|#M z8;%q#_>R434I z&8~;^S_L+i`e5zImPub$UhD1fexBk~RV5e>BlTzz!&cQ^J`r4eLJASi2{xH`< z)TP7fSJ94ClxLPiZzEO!BLAPINhCh2a7yUkJ)S@QaD6E0rOs7GH#B*mQcm^ANlo&**+d&9zZWO_geOh zTv;l^3H^2#sqQ6**nzJdKKr`N>$LXu^Fw!_f@u=*?wkB7BhOTs!Q| zQLnW|+z*>eZ0Wc-3yt%=eFalUfgabV*jZvdo4X-7P#FKsd72m zl*<|(RdQ-6OsV*dgD3BYp&1KwZ%i!(@lR;Z4Q=o4mRRmEz1vYvJcSij{l>9gJg08l zlvcj7kVoOix)|JqVypf)l*$i_`ca_b`Hg#9!9fk7Fsq;?52bD)MZhDc7ANs#{PV8) z!zrQawbWGE*xIP>R<5OBscajj6b)0T0(Gd$8YH^5C~%y+KANr{)RfraE1z=xTpvn$ zAUz?i^Sg=hUsV_(g5Z>U++5t;^c-!`Z^HOE!sI#@XN9KTN)=U-Mf=mzE^GS)lE}7~ z+l2hylDi*hW9MruV_Qx?Dz8INpWtAM3d@-b`p4)7n2Q*R`M(}cVWmY>_q-M333#|E zjRa4~JgVWx@r3WE{GL2aZ*T0|xsez4UDO%|+Sezo+sDg^Eqmx|D7ONhk=(1Lh2`_{ zP23#h9D>Y(9)+Ll?K_IzbcrHYFF)@7`SfP{-8f2PpH(9Hz@#c%nM|-W48*$qVX}oK zUM;>Vfxz*MRyvMahojJ0@&qF*0Ar9@==`XB1z~HIt(bfBsd#EhueI$z&JU6 z>G9A7fdk+Mzuw>(Gr4Xj*SGyR5PAOTwe*;fML$%>C=OxlGRFDJ|#@p7gqQ;{5HWy8nUGcgPk~{_Q^~RPQA-ym< zEGk^Fjkyj7%Gk#0qs;Mrj$r1cDz6&p$VIDWt9q6qQQehE2Z_RQb-rfSYya0IZY@8h zBA?rV%xLm`X>`AF-)s4@-}otD>2?!M*(ya$PzINyiryV=`JueYODbKq+|^@qou(~F zWN#tW@YU9;&ij|j%emYt`j>;eKIjHiMgGUSFHL9LhqSG4@`+E6G-F_1hf()DTT}nw zboj88ce4C^Enh!o4}t+sCMQ|s*|J_b_y0Op;>I)$Fwm?$F7ChUwanT`_puInYS(eN z?@aEra_^&_JhPt6u;|qimLkIR{>|YIPv-5-;F$d3@K|w%X)I-ESs7iP__!IU5c0?- z#(`T6@n)3?lEI(w`k22t`rWxS_Y-{!C%pIQfKC~qjVjc^(62Z-ElS9?TW9RYdC6O+ zz3wW;r;^58r#AC;Rol8-#m(X#{pZ7Ai38^scB^f$8(Cw%Q$9v&QDJ4>-|}?b^QY~T zlN855T~pL}M6oX_hyy4xX|Q z8Ko%yzL<(gFF`nHC-wbw*|tB3B1tJhHOXXfxk`j9e0P^3VUMsl=OuL_i#Mzx^~58g zT1MouwEp7pyuQvGFT}a&!~C?8bR8%9jSL>Pqa8$}A7ESg6m(MaN002S7wYLK()tW5 z!blx_OZwNGKj~TDYHfCx2G$)9F1ud&&tZs}AAE-q0T~OmACz=Zuv&;MHt&A*zDm|^ z(0s5n7-ha&l#8xnTbM|xvA6KMVho;9VHV^SWy3S7j8~g{=HNd?p#Ebs>~OfqOnu@ z?^o>)kvSL&7Qc4^ei)^DycFl_pkZnRWbbDcAWQx&;Ftv46x1E&<@YrRF#C;@R9l^- zOg1Rt#?Hm9oNk)N_xWR{-F9{hCAPd$%W&Dq;tItNpz@a1Ms@bS$=V}A^xepG<9 zTOD%LV@LIcJgq#ud@@3N6cvUbru@;L2`tJhD!M=H8#`rG@YV>QJ&l)7dW!_&Ye*+7 z=?an4i74Nobb<4tEz=DvV_UpwqfkZN)85~6du}cfdVlOP+ff`Cn2MPmY_RgmjKDib?r3kw(GZL^Q7Ca2xnp6S_?`2d?}u zS?3VwgM$(4UrrGuZF{|eu)q3V;uGy+5Bu;*px4dorvh@Vn8U$&)q}F!lLa!ufGd)? zx5;)ZjyIpQv$aLal2w~Uy-~B)LN3bXLO#2>ED9T0+7b_O>BsqjDO(}8+)>nDgZsaM zj#&}k?)szDAqdrL?@-CRb2x<9kAKztsd;UkkcD?e{0}3}XfpZu&-v(hW+A5K?Om6V zWhEqo_B`78k!JIsdD4fYUNhWie-Zu<%M!XN4X1SXDwv&FWXu}1`d-V=4`od(UpHrG z=RSKH+qsD3mGsA_JKu2=#hZRSNS%MMF;EjzS-j^&{1$qfL zzxODlQtgS>ZPjMk@51|UopU{UxZ%JbZS=dmb$9=@Zj+bZvXI-}CFpS6^Zri&;2Iz0 z_qgVwYh!J(7j?gA#(R&^TbIy2z4OTL55GM<+!*o){qrCFpmTEP%g-MkYmFV$bI^=; zAER!gUepinIe6j91@!1tRkd%&zVw=$!OV#MBVPIPWonpens#ZQq1zAdI1#-%=XNa8 z7^lWE9D^AIXkXsGhyy|++O|Vmn^?FEmtU55?M2seEI0T2c^|L-xVo-d_b2kndbMXTg_3t`h@-_D?eeSzOYZtHIy`K7#FD#n#6~2=N<>0JOXRY12X7*>ZN~%j9pa1Xj+H$JI^n=MO z&!4}1eoI*hEy2myl_Pr!Zc zl#AB5%kRHnHHa3@2hv1qb}%jL2lX67Ss`_92Q5nl&*G@{k%@+t>3f$PX|;rwO2+jo6*)it$UOus$Zw`*6;a+cDX#x zf4%#!2d;lGBO${d@C6m=zhAvpQdJB9Jj+dfdGZ6-KhQO$i(d-Vd0mgrd$iJ7(cX>s zlXfRx`uxRjH-3|mkbwZr`(bW&aW=-708v%-NKw|BoohlSku1vqfDq^e-4nMx$#GnV zgbvfkPUl%}@!G|NXqu+2+`4k>!L7caZ~2zx`4#yQS9^bO(W$Q3wqje>Hn-osdi(0! z(p-!i(q%Y?U9n}^;le}t<@ukj`Rr)PQI6$IdefhxdP{#@da&T&(UPNIuKOadERSP3 z-bmd>R9I0s?ais3lRF!9hMh-t3cNtSK#XvHdENv6y+1QC)2K78DSPkAM_-@bw2(RS|e023$*CRm9^CMc>URlh#yeU_kYX8@{DG9JseiOW`X`E}P!7)~QmRU0qnk@nVkT8pBY@V>fUV7? zD$OsITai*#71}M1#W2jhqwZ~PA>b-T5RyM=y z1^t7gr__1sBBUfmHXF@{c$PY4P9)i`rHY< zigQY_R85_|Xx5RUBh8P(6?0b3U-s^roogdpEq~Cz_&35mFw%XX6_iXMHv+w=6e8${-qk zd};MFA3amh3`ceQ+`qrK^SMu+EvzhzkTU5_KfV0(WAh)Y^E6Ab_to$FaR1DOPqmn0 z|MM$r9(>~gwi#!3y|@0rM+cw$@QD^v>{q_Hs=e-52pHT_iapT~LKrhF!$v4pL)tt3 z!xVek6`>*=!?lw3j{ZQ3WmrJKw3N=@7>CnBvOJBJX+?@>`Dkmj)9nli>P0;bnqV20 zVHj0aM{gro#cu$%1Fb=7pMCWILECRvf-?-cvLTWAOR&z=9N_tnd)EUU6V z=pS>*n6$XGMx;vj-v?ixJAK~M6Q5rEEzC&B)HIFW%76UtkF7@A z-(F!WhE@a5Y>SsUk$&GFcb(8>ev2c}>YA;fqoG+sbA|q-k-G6XR)HWfDrw5-DICiK zfTpPzcE4!#j@9(={pqFEZ!dj&@+*^oz>q#eR=lwC`X{d=g!ls9hu)g@-ox+R{OowQ z-~H+TefGif_seR_{#L9%Y$m;_S9-626ez7J%}nf|s!H=~HAPhdQXnZhsm@#1{LRsz zrG{j{lY6WT+pl9kgJ^VloV#*%HBVKgI+Pflm>!n_00;8-(<3`AJ}oIa83EdIaI>aq zCecJu{?-B2K+wNo?*<*O0{~f7lA@F7vB)y)Z-=&y=zrCw1Dh&qE5}?iCbu-#?RC?7 z;=6NpKlsK2b0*Izsw$rM{oEz%mmE)(`VRZWUcy0F`YQ*WGh+jF=6tyrz-mVd^(kLd;dz1Q70?airEZkTG+ z8K^P-&h)uG(|i81XWgQo7Ttcu?SrNal4PmHdka~X6-EC4_O3fVs$y-=oU&(Yb~l@B z5_)JCFd!h(d+~-4q!%@S1Q1lJm!N=pB}lmh5k!iL2#JDpgaDz4Xh<-$Knfr&5J=Bv zdp+mOe1A-`E}OuoT<-5)A@gT;Cv(o(=e*^a_j%_}`Cq@-)@U_OPMD-q>k{WDO?&xG z5s8yl#FC71$A>#r3Kc;xLZXzF@}fWzg;G;#?26ca?fOj`G@0c8elW@AjizXi;PKi{i^CGTVxq5`evHl^&la#K3Nkc<0j{tZ zScdhcGlD2AN&0wb_n`wjMTLhp)+n_t8n(E0_ZkVxf;GWjdPziLfFi)+uy~(1(Lu;K znJ9|YIUbJXS~O^J@y118GK^(dxm1o2dhO#^!*pQ?A+oV3B2gQloxWze)9nBN2%@+k zW&u;Bv!E%8bcd5m<#mJV`ibbtH8iQy^zyAszH5bthVufSUz(2r4ps-(*Vj+aO($30 zFr=ZyVKG{b9>Ei&4yqqq-%DE%tLDAOkiZZ>4puHz)YsR$dgtn6xX7msSPadiUjD`D zb{Z^(b^F&zS?RU>s}!UnI}U^p78(lI?_dAL!7qX}!J*pFm%9!z+suR$AJKC}qp)yq zIIoZ@(sR=wfCO{N%MLkQ4w|A#W-x9^jIRR?(}iuFpV;`7@Bl?+ZHbF_UAc25L>od_ zfFeT?ffNB;O2(0UrTLW(5uwzq)H_9YAb>93bu#lL%dpvb*#>j@+_7;S%USFe-y5v2 zZ?Iv`hIUihdJlt8E7VJ;ESvrLEMDMSH)*}|&N-7j9ZmJ!|1b{?&f3Zu@b&?;o4iX|iPU(!THXQAicfIjOO>Aot zc;)8hT)uT#5QS=IWno`W=;1wcH~d2?krLU0?_QovB1_njK!_sJlViup?EwlkgwW}B zuG_ax!blu0$MiMR0j%r>;1~1zIY)+O%y!ew!kgm6WBZOJ!VuDlq|8)4 zve(E-gC>tzGAQa3P1BTb zS5FME%i|i^Yh;@y&u{!;Ly@6~1Q~AL{Yt-Aj$|Am_23AjPAxmRc{hRPn7(5J0|F63 zFSdB``n~H?iPYP5!yA!9;CGZb;3OP`5MlH}^A00>jd1gxgzr}n+c3Px@GcQuMI>%Y z{wlvDKeFS%B0~|eA`_!xBN|0O2sa=8`uhFrtsA$_d$1F3bzwdLK*DhAXRR+Wl@9x0 zn9XTxQm2WR#n31e){~YNNJYj)h#4f{PA9CJ_#ct!XxItwxHBlnsMdVq=n!9bm?Tm_5@;J zM)w|b>e49!ER1m1=ek-PR`R|=2q;KVG?jV%Vv(V+`l2?c^+5Uo-!eJ~p;D?Op`O)Y z$-I%-rB#>Ez)%2y)9su)Hg5IqPjvx7BYKWlzHM3m4*k#nb^&9oRcc4~8nt@&>eh{0 z&l&a3q!qDUT6OVNOps9h;F*KI%y@;RBJ*aZ(PG?uO4nj&Ul~0L&L;HIS2b~_LUyNn68IPVz`Lnx(Pc5*Bg87HE z9|8jc7fp3hpT9x*-{xPd|ed0~#X-F$%itaPIc{S6qQIovwBxstZDH=ei*M_uj)?TmC z!>S_=DT<;gT0ml8fUbSB_Vt45`K_hb=!bS6x_5*2XPxre_8@hT%j1FoQV?d| z$Q-ipIW9#q;m&c`6YU0MuoBjE$Qd(VI4Au5^~9rzyvPFpL{S{nIofPDV}O-1<)F^ddw$+i2`o|U(WZxArL|t8 zf2H56UnYN93oO7;OlnptO{G_QE>_}AtT4u>vs1~w2YG!ftQ9V|%bU%)Jg&<(FJlbN?Mo3KlHMxrKb@*)r`PGYR6zVP!-CkcdzRX+(Fo@eQ zH(^==iRSMT*y*>b3f$<4Gi14^(Lz*J)g6D8v|t>Ovdn8~V0DB1XM>ZR)d| z70wt@Ht#;fE3CYv!dFm&C%bwqmuFmSjX(etCp ztHq1_w+p@{M$~9EE>2v$XyPK4X3Y*uSYYUy-D~cZJz%jY2p=!NdJ2KX3w+zAZO1Gc zt&*$ej*06y?Zp`*X7+5;17j?g%18Db(P3%_tx7wr$MBc|G24IK)~s$b!a+oL9z1Q; z8xltHTL0HZ_8N8Z`o+HO`jTS#U8i=YWuLY>tOwJQuiVYW*njt?xT1L0+L^v$d5-12 z|8U21;mTumn*!e*QQ`Q8(`27|@~rv2({u zDoV*jmSL069MY+SvTkK{eXh&iU-o$SjywXNa_%TiF*$c~2X&50K9gK-G5CSl?y{%l zK5(`h#+dZ6NzF!)e@{BP$7<{WN^AF!+r z>$%^~Jsy?y6avd5ct~5RT|ez)S;_A`x3O4mF5C6d?!CY4`R>Gb9Lo*sF|6`*H3(gT zYj6C%O@}rm9ZL*Q1hjA7{`-^Ly+7*zE9mp@8v~()Wf4XUP4j}_dk7Q;SSFE*f|FB0I)bLdE4*Y zF1qdZxXBetCDOQ!aip?u_4G9-E}U)F6W#y-1zJf&K~zXSbC6}&X@jTrXw!4T^6`&e z>)8dCjFZK0U1YQw5kePkT#zu5{ipY1jE8m~$_xC=&t}Rv+5NKnWb=^>_qN;?b?i=?85t+m@qSiwuQTor@ne%yx6qvBWo`-&ntYU20Y; z+0Y~_$|SO--!2*V()bOB*JoVKAZ$80S|LyssFW)ApWZL$WQp?=Q_iMj=Vb+{gPzn6 z@wAmDLr9gG%1DGk-ZrblYH?V|E?~L2yr8_mYk2aC^2j$K04AHM#8^Tsgdhs}rTPDk z4w#Ttc9$JvTxu%ybD%U$mzm0VkuR<&et@qBkf9l?(~2=JsVJ^deq9sA0hS?l`q${SWJT zK>z?gsUM{IRc&wiKKW9orf0;-hQSDz&!5kF)Wk=Ub}))ElWwhJN>A11M{aV_Gwtxl_- z(-esM9*Z?>Kq$&*N^HP)vj4P{~(E?)QW(}j*;PE;eN9`ygMu~EV@f{u}@T96`QjvxhlHL z;E3?n)eakX1*?OjyAG~?M7dPLDWr;_J%*Q=N;?lao% zvR9ks43Gy5?LN$0P0lZu%B2!%%zzjOVf9%GPBFaaaGTR#m@nsKYK7*HV+;PN;|Bg` zM}@Uw#D^oobYWhS#t&LbsmkGTASC+7rx?qma<|7_Q$-tAN>vWG1L2xxadJ0b<1C<* zDeX>M4Ue^QEf=cknLlEdT&i$+oYiMR0F*M7)nNhE^9+m?GKJIq&{?E+S532&PPe0` zE5rbU!@nM;0sTkN_4HUQnxb;^a&q#r$ysVTwGJUHA|bydpVSjWP;-v;7ybwX4DiFU z*C#v+0Du8QGuCvQXcM0yV1cSVJMz@_#_t0FY&!JSXL~=RAoZv``mg)Zqsf6MJBxgT z0i*$BAOL`6S&>wdJqtV*fmgY?{Qa*N-Y&eoeB1KJ=8kK}BZgcqm&s&GrPA*lm#3U= zU})y(*`uc}o${VPSUaBVt4mQ7iOz}LdWLu`UgUWv51$;BTswT@Fi{j4hQSznJf3IB zV%3iS^l&_*0nx-F5O@cQS4VZDk4!ApbORn*9)$vRchNbX#*fMn92*v3$pXFD=ZaOht=tEKDHdb7Fd7n z!AZF8t$V!C{Dr)t+bWsLXfrkrZG5-nu3D*a^PZ5v5S!CFu+u=R!@B#I-K4=4002Tb z{-yC+mG;b)GYrKvt=m+>awQcd5#bTH3UBpk+sE#**Vi}5FU>!ge!x5HzdrsC&?GQ- Ty|O-`00000NkvXXu0mjfBij1Z literal 0 HcmV?d00001 diff --git a/DOC/HTML/images/LDR-gnup-1.png b/DOC/HTML/images/LDR-gnup-1.png new file mode 100644 index 0000000000000000000000000000000000000000..6ec61943d3f6a8ed1341fef77b34b035771c511a GIT binary patch literal 47487 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") + t = snafu(t, "\n
", "\n") + t = snafu(t, "
", "") + t = snafu(t, "\n
", "\n") + t = snafu(t, "


", "

") + + f = open(n, "w"); + f.write(t) + f.close() + diff --git a/DOC/bin/updatesql.py b/DOC/bin/updatesql.py new file mode 100755 index 0000000..d8acfc9 --- /dev/null +++ b/DOC/bin/updatesql.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 + +import sqlite3 +import glob + +db=sqlite3.connect("dbase/lg.sqlite") + +c=db.cursor() + +for fn in glob.glob("tmp/body/*.body"): + f = open(fn, encoding='utf-8') + body = f.read() + f.close() + c.execute("UPDATE lg SET body=? WHERE file_name=?", (body, fn[9:-5])) + +c.close() + +db.commit() + +db.close() + diff --git a/DOC/cdoc b/DOC/cdoc new file mode 100755 index 0000000..7f8bf77 --- /dev/null +++ b/DOC/cdoc @@ -0,0 +1,24 @@ +#!/bin/bash + +mkdir -p MAN + +rm -rf tmp +rm -f HTML/*.html +rm -f MAN/*.[13] + +echo ">rgs" +bin/smakdoc.py src/defs/rgs.def man >MAN/rgs.1 + +echo ">rgpiod" +cat src/defs/{rgpiod,permits,scripts}.def >temp.def +bin/dmakdoc.py rgpiod temp.def man >MAN/rgpiod.1 +rm temp.def + +echo ">lgpio.h" +bin/cmakdoc.py lgpio ../lgpio.h man >MAN/lgpio.3 + +echo ">rgpio.h" +bin/cmakdoc.py rgpio ../rgpio.h man >MAN/rgpio.3 + +cp MAN/*.[13] .. + diff --git a/DOC/dbase/lg.sqlite b/DOC/dbase/lg.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..820dc51bf8dfaafa3ca23f6546fd72467627eba3 GIT binary patch literal 1433600 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%0AQ329)g2rjI;2elc8 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

C(^?!2y_5bSTSFp2~xvzg6 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 z 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 X0YN|z5CjAPK|l}?1O$PP4S~M_{i0+` literal 0 HcmV?d00001 diff --git a/DOC/hdoc b/DOC/hdoc new file mode 100755 index 0000000..0f49286 --- /dev/null +++ b/DOC/hdoc @@ -0,0 +1,42 @@ +#!/bin/bash + +mkdir -p tmp/body + +echo ">download" +bin/dmakdoc.py download src/defs/download.def >tmp/body/download.body + +echo ">faq" +bin/dmakdoc.py faq src/defs/faq.def >tmp/body/faq.body + +echo ">index" +bin/dmakdoc.py index src/defs/index.def >tmp/body/index.body + +echo ">rgpiod" +bin/dmakdoc.py rgpiod src/defs/rgpiod.def >tmp/body/rgpiod.body + +echo ">permits" +bin/dmakdoc.py permits src/defs/permits.def >tmp/body/permits.body + +echo ">scripts" +bin/dmakdoc.py scripts src/defs/scripts.def >tmp/body/scripts.body + +echo ">rgs" +bin/smakdoc.py src/defs/rgs.def >tmp/body/rgs.body + +echo ">lgpio" +bin/cmakdoc.py lgpio ../lgpio.h >tmp/body/lgpio.body + +echo ">rgpio" +bin/cmakdoc.py rgpio ../rgpio.h >tmp/body/rgpio.body + +echo ">examples" +bin/examples.py src/defs/examples.def >tmp/body/examples.body + +#bin/body.py # get bodies of manually generated pages +bin/tidy.py # tidy the bodies +bin/backup.sh # backup database +bin/updatesql.py # update the database with the new bodies +bin/purge.sh # remove redundant datatbase copies + +bin/build_site.py # construct the web site + diff --git a/DOC/makedoc b/DOC/makedoc new file mode 100755 index 0000000..c48c78d --- /dev/null +++ b/DOC/makedoc @@ -0,0 +1,6 @@ +#!/bin/bash + +./cdoc +./pdoc +./hdoc + diff --git a/DOC/pdoc b/DOC/pdoc new file mode 100755 index 0000000..bb5c79c --- /dev/null +++ b/DOC/pdoc @@ -0,0 +1,13 @@ +#!/bin/bash + +mkdir -p tmp/pydoc +mkdir -p tmp/body + +echo ">lgpio.py" +pydoc3 lgpio >tmp/pydoc/lgpio.pydoc +bin/pymakdoc.py tmp/pydoc/lgpio.pydoc local >tmp/body/py_lgpio.body + +echo ">rgpio.py" +pydoc3 rgpio >tmp/pydoc/rgpio.pydoc +bin/pymakdoc.py tmp/pydoc/rgpio.pydoc >tmp/body/py_rgpio.body + diff --git a/DOC/src/defs/download.def b/DOC/src/defs/download.def new file mode 100644 index 0000000..b7419b4 --- /dev/null +++ b/DOC/src/defs/download.def @@ -0,0 +1,76 @@ +/*TEXT Preliminary + +A few packages are needed during installation of the Python modules. + +[ul] +the SWIG code generator (to build lgpio.py from the C library) +the Python set up tools (to install lgpio.py and rgpio.py) +[ul] + +These packages may be installed with the following command. + +. . +sudo apt install python-setuptools python3-setuptools swig +. . + +TEXT*/ + +/*TEXT Download&Install + +The lg archive may be downloaded and installed with the following +commands. + +. . +wget http://abyz.me.uk/lg/lg.zip +unzip lg.zip +cd lg +make +sudo make c +sudo make python +. . + +TEXT*/ + +/*TEXT To check the library + +There are example programs in the EXAMPLES directory. + +[ul] +lgpio - C - local access +rgpio - C - local and remote access via the daemon +py_lgpio - Python - local access +py_rgpio - Python - local and remote access via the daemon +rgs - Shell - local and remote access via the daemon +[ul] + +TEXT*/ + +/*TEXT To compile, link, and run a C program + +C (local library API) +. . +gcc -Wall -o foobar foobar.c -llgpio # local access +./foobar +. . + +C (remote library API) +. . +gcc -Wall -o foobar foobar.c -lrgpio # local and remote access +./foobar +. . + +TEXT*/ + + +/*TEXT To start the rgpiod daemon + +rgpiod & + +TEXT*/ + +/*TEXT To stop the rgpiod daemon + +killall rgpiod + +TEXT*/ + diff --git a/DOC/src/defs/examples.def b/DOC/src/defs/examples.def new file mode 100644 index 0000000..0b5cea8 --- /dev/null +++ b/DOC/src/defs/examples.def @@ -0,0 +1,54 @@ +The following examples show various ways lg may be used to communicate with sensors via the GPIO. + +Although many are complete programs they are intended to be a starting point in producing your own code, not an end point. + +?0|Index + +?0|lgpio C + +?0|lgpio Python + +?0|rgpio C + +?0|rgpio Python + +?0|rgs shell + + +?1|C|lgpio C + +Examples of C lgpio programs. + +If your program is called foobar.c then build with + +gcc -Wall-o foobar foobar.c -llgpio + + +?1|Python|lgpio Python + +The lgpio Python code may be run on any Linux SBC with Python installed. It allows control of the local GPIO. + + +?1|C|rgpio C + +Examples of C rgpio programs. + +If your program is called foobar.c then build with + +gcc -Wall-o foobar foobar.c -lrgpio + + +?1|Python|rgpio Python + +The rgpio Python code may be run on any SBC with Python installed. It allows control of the GPIO on one or more networked SBCs. + +The Python machine need not be Linux, it may run Windows, Mac, etc, anything as long as it supports Python. + +Each SBC needs the rgpiod daemon to be running. The rgpiod daemon may be started with the command rgpiod &. + + +?1|rgs|rgs shell + + +?1|Index|Index + diff --git a/DOC/src/defs/faq.def b/DOC/src/defs/faq.def new file mode 100644 index 0000000..64f85df --- /dev/null +++ b/DOC/src/defs/faq.def @@ -0,0 +1,4 @@ +/*TEXT + +TEXT*/ + diff --git a/DOC/src/defs/index.def b/DOC/src/defs/index.def new file mode 100644 index 0000000..08bb299 --- /dev/null +++ b/DOC/src/defs/index.def @@ -0,0 +1,46 @@ +/*TEXT + +lg is an arhive of programs for Linux Single Board Computers which allows control of the General Purpose Input Outputs. +TEXT*/ + + +/*TEXT Features + +[ul] +reading and writing GPIO singly and in groups +software timed PWM +callbacks on GPIO level change +notifications via pipe on GPIO level change +I2C wrapper +SPI wrapper +serial link wrapper +socket interface +permission control (socket interface) +file handling (socket interface) +creating and running scripts (socket interface) +network access (socket interface) +[ul] + +TEXT*/ + +/*TEXT General + +The archive contains the following: + +[ul] +The lgpio C library to control local GPIO. +The lgpio Python module to control local GPIO. +The rgpiod daemon offers a socket interface to the lgpio library. +The rgpio C library to control local and remote GPIO via the daemon. +The rgpio Python module to control local and remote GPIO via the daemon. +The rgs shell utility to control local and remote GPIO via the daemon. +[ul] + +TEXT*/ + +/*TEXT GPIO + +ALL GPIO are identified by their gpiochip device number. + +TEXT*/ + diff --git a/DOC/src/defs/permits.def b/DOC/src/defs/permits.def new file mode 100644 index 0000000..149e62c --- /dev/null +++ b/DOC/src/defs/permits.def @@ -0,0 +1,313 @@ + +/*TEXT Daemon Access Control + +The rpgpio daemon operates in two modes - with or without access control. + +The default setting is without access control and the permissions +system does not apply (so the rest of this section may be ignored). + +If the rgpiod daemon is started with the -x option it implements +an access control permissions system to its functions. + +There are three parts to the permissions system. + +[ol] +An .lg_secret file in the users home directory. +An .lg_secret file in the daemon's configuration directory. +A permits file in the daemon's configuration directory. +[ol] + +The daemon client "logs in" to the daemon by choosing a user name. If the client and daemon copies of the password for the user match the user is "logged in". + +The client program is then authorised to carry out any functions permitted to the user as specified in the permits file. + +TEXT*/ + +/*TEXT User secret file + +The user .lg_secret file contains a list of user names with an associated password. + +These passwords have no relationship to the passwords used by Linux and should not be the same. The format is user=password. + +An example .lg_secret file. + +. . +# user secrets file +# user=password +pete=t4pf4kvPOXjLfDnKBrMu +. . + +The file should be readable/writable by the owner only. + +chmod 600 .lg_secret +TEXT*/ + +/*TEXT Daemon secret file + +The daemon .lg_secret file contains a list of user names with an associated password. + +These passwords have no relationship to the passwords used by Linux and should not be the same. The format is user=password. + +An example daemon .lg_secret file. + +. . +# rgpiod secrets file +# user=password +joan=kr6g89XmFQvLDWh6UcJH +sally=fARrxSKqdHaPHBu6Vtet +pete=t4pf4kvPOXjLfDnKBrMu +fred=tugXUuRdPqGux6t7jhhv +. . + +The file should be readable/writable by the owner only. + +chmod 600 .lg_secret +TEXT*/ + +/*TEXT Daemon permits file + +The permits file can contain the following sections. If a section is +absent it means that access to those features is forbidden. + +. . +[debug] +[files] +[gpio] +[i2c] +[notify] +[scripts] +[serial] +[shell] +[spi] +. . +TEXT*/ + +/*TEXT [debug] +Each entry in this section takes one of the following forms: [#user=y#] or [#user=n#]. + +If the form [#user=y#] is used that user is allowed to use the debug commands. + +If the form [#user=n#] is used, or there is no entry for the user, that user is +not allowed to use the debug command. + +If the [debug] section is not present no user is allowed to use the +debug commands. + +The debug commands are set and get sbc internals and reload configuration. +TEXT*/ + +/*TEXT [files] +Each entry in this section takes the form [#user=path x#] where +[#path#] indicates a file path and [#x#] refers to a permission. E.g. +[#/home/peter/data.txt r#] refers to Linux file[#/home/peter/data.txt#] +and read permission. + +There may be more than one [#path#] entry per user, each must be separated by a [#:#] character. + +[#path#] may contain the wild card characters [#*#] (matches any +characters) or [#?#] (matches a single character). + +If the path entry starts with / it is relative to root (/) otherwise +it is relative to the daemons's working directory. + +The permission may be R for read, W for write, U for read/write, +and N for no access. If a directory allows read/write access then +files may be created in that directory. + +Where more than one entry matches a file the most specific rule +applies. If no entry matches a file then access is denied. + +... +joan=/tmp/* u:* n:TEST/* r:TEST/TEST/* u +... + +User joan may create, read, and write files in the /tmp directory ([#/tmp/* u#]). + +User joan has no access to files in the working directory ([#* n#]). + +Overridden by user joan has read permission for files in the TEST directory +of the working directory ([#TEST/* r#]). + +Overridden by user joan may create, read, and write files in the +TEST/TEST directory of the working directory ([#TEST/TEST* u#]). +TEXT*/ + +/*TEXT [gpio] +Each entry in this section takes the form [#user=x.y#] where [#x#] indicates +a gpiochip device and [#y#] indicates a GPIO. E.g. [#1.2#] refers to Linux device [#/dev/gpiochip1#] GPIO 2. + +There may be more than one [#x.y#] entry per user, each must be separated by a [#:#] character. + +Both x and y may have the following forms. + +[#*#] all gpiochips or all GPIO. +[#n#] a single gpiochip or GPIO. +[#n,n#] a list of gpiochips or GPIO. +[#n-n#] a range of gpiochips or GPIO. + +... +fred=0.2-27 # user fred can access gpiochip 0 GPIO 2-27. +peter=*.1,2 # user peter can access all gpiochips GPIO 1 and 2. +jill=1,2.* # user jill can access all GPIO of gpiochips 1 and 2. +boss=*.* # user boss can access all gpiochips and GPIO. +sally=0.2-27:3.* # user sally can access gpiochip 0 GPIO 2-27 and + # all GPIO of gpiochip 3. +... + +TEXT*/ + +/*TEXT [i2c] +Each entry in this section takes the form [#user=x.y#] where [#x#] indicates +an I2C bus and [#y#] indicates a device on the bus. E.g. [#1.27#] refers to Linux device [#/dev/i2c-1#] device 27. + +There may be more than one [#x.y#] entry per user, each must be separated by a [#:#] character. + +Both x and y may have the following forms. + +[#*#] all I2C buses or all devices. +[#n#] a single I2C bus or device. +[#n,n#] a list of I2C buses or devices. +[#n-n#] a range of I2C buses or devices. + +... +fred=0.3-127 # user fred can access I2C bus 0 devices 3-127. +peter=*.83,89 # user peter can access all I2C buses devices 83 and 89. +jill=1,2.* # user jill can access all devices on I2C buses 1 and 2. +boss=*.* # user boss can access all I2C buses and devices. +sally=0.80-99:3.* # user sally can access I2C bus 0 devices 80-99 and + # all devices of I2C bus 3. +... + +TEXT*/ + +/*TEXT [notify] +Each entry in this section takes one of the following forms: [#user=y#] or [#user=n#]. + +If the form [#user=y#] is used that user is allowed to use the +notify commands. + +If the form [#user=n#] is used, or there is no entry for the user, +that user is not allowed to use the notifiy commands. + +If the [notify] section is not present no user is allowed to use the +notify commands. +TEXT*/ + +/*TEXT [scripts] +Each entry in this section takes one of the following forms: [#user=y#] or [#user=n#]. + +If the form [#user=y#] is used that user is allowed to use the script commands. + +If the form [#user=n#] is used, or there is no entry for the user, that user is +not allowed to use the script command. + +If the [debug] section is not present no user is allowed to use the +script commands. +TEXT*/ + +/*TEXT [serial] +Each entry in this section takes the form [#user=device#] where +[#device#] indicates a serial device. E.g. [#serial0#] refers to +Linux device [#/dev/serial0#] + +There may be more than one [#device#] entry per user, each must be separated by a [#:#] character. + +[#device#] may contain the wild card characters [#*#] (matches any +characters) or [#?#] (matches a single character). + +... +fred=serial0 # user fred can access /dev/serial0. +peter=tty* # user peter can access /dev/tty*. +boss=* # user boss can access /dev/*. +sally=serial?:ttyS* # user sally can access /dev/serial? and /dev/ttyS*. +... + +TEXT*/ + +/*TEXT [shell] +Each entry in this section takes one of the following forms: [#user=y#] or [#user=n#]. + +If the form [#user=y#] is used that user is allowed to use the shell commands. + +If the form [#user=n#] is used, or there is no entry for the user, that user is +not allowed to use the shell commands. + +If the [shell] section is not present no user is allowed to use the +shell commands. +TEXT*/ + +/*TEXT [spi] +Each entry in this section takes the form [#user=x.y#] where [#x#] +indicates a SPI bus and [#y#] indicates a slave select. E.g. [#1.2#] +refers to Linux device [#/dev/spidev1.2#] + +There may be more than one [#x.y#] entry per user, each must be separated by a [#:#] character. + +Both [#x#] and [#y#] may have the following forms. + +[#*#] all SPI buses or all slaves. +[#n#] a single SPI bus or slave. +[#n,n#] a list of SPI buses or slaves. +[#n-n#] a range of SPI buses or slaves. + +... +fred=0.0-2 # user fred can access SPI bus 0 slaves 0-2. +peter=*.0 # user peter can access all SPI buses slave 0. +jill=1,2.* # user jill can access all slaves on SPI buses 1 and 2. +boss=*.* # user boss can access all SPI buses and slaves. +sally=0.0-2:1.* # user sally can access SPI bus 0 slaves 0-2 and + # all slaves of SPI bus 1. +... + +TEXT*/ + +/*TEXT Example permits file + +. . +# rgpiod test file for user access +# user=permission + +[files] +default=: +test1=/tmp/* u:* n:TEST/* r:TEST/TEST/* u: + +[gpio] +test1=*.2-27 +test2=0.2-27 +test3=0.5-10 + +[i2c] +test1=1-999.* +test2=1-2.* +test3=2.5-20 + +[notify] +test1=n +test2=y +test3=y + +[scripts] +test1=y +test2=n +test3=y + +[serial] +test1=serial*:ttyUSB*:ttyS* +test2=ttyUSB1:tty0:ttyS0 +test3=null + +[spi] +test1=0.0:0.1:1.0:1.1:1.2:2.0:2.1 +test2=0.* +test3=*.0 + +[debug] +admin=y + +[shell] +test1=n +test2=n +test3=y +. . +TEXT*/ + diff --git a/DOC/src/defs/rgpiod.def b/DOC/src/defs/rgpiod.def new file mode 100644 index 0000000..b265e11 --- /dev/null +++ b/DOC/src/defs/rgpiod.def @@ -0,0 +1,110 @@ + +/*TEXT + +rgpiod is a daemon to allow remote access to a SBC's GPIO. + +Once launched the daemon runs in the background accepting commands from the socket interface. + +The daemon requires no special privileges and commands may be issued by normal users. + +TEXT*/ + +/*TEXT Features + +The following features are available by issuing socket commands to the +daemon. + +[ul] +reading and writing GPIO singly and in groups +software timed PWM +GPIO callbacks +pipe notification of GPIO events +I2C wrapper +SPI wrapper +serial link wrapper +file handling +creating and running scripts +[ul] + +TEXT*/ + +/*TEXT Launch options +rgpiod [options] & + +TEXT*/ + +/*O +-c dir |set the configuration directory (default current directory) +-l |disable remote socket interface (default enabled) +-n address |allow IP address to use the socket interface, name (e.g. paul) or dotted quad (e.g. 192.168.1.66). If the -n option is not used all addresses are allowed (unless overridden by the -l option). Multiple -n options are allowed. If -l has been used only -n localhost has any effect +-p value |set the socket port (1024-32000, default 8889) +-v |display rgpiod version and exit +-w dir |set working directory (default launch directory) +-x |enable access control (default off) +O*/ + +/*TEXT Permissions + +The rgpiod daemon has an optional access control system to control access to its functions. + +See [+Permits+]. + + +TEXT*/ + +/*TEXT Scripts + +Scripts are programs to be stored and executed by the rgpiod daemon. +They are intended to mitigate any performance problems associated with +the rgpiod daemon server/client model. + +See [+Scripts+]. + +TEXT*/ +/*TEXT Socket commands + +Each socket command consists of a header and for commands with +parameters an extension. + +The header is a lgCmd_t with the following structure. + +. . +typedef struct +{ + union + { + uint32_t magic; + int32_t status; + }; + uint32_t size; + uint16_t cmd; + uint16_t doubles; + uint16_t longs; + uint16_t shorts; +} lgCmd_t, *lgCmd_p; +. . + +The magic value is 0x6c67646d (ASCII lgdm). + +The size is the overall size in bytes of the optional extension. + +The cmd is the command code. See the file lgSocketCommandCodes.h +for the command codes. + +The doubles, longs, shorts is the number of 8-byte, 4-byte, and 2-byte +quantities in the extension. This information is used to +network order the bytes of the message. The extension should consist of +doubles 8-byte quantites, followed by longs 4-byte quantities, followed by +shorts 2-byte quantities, followed by as many 1-byte quantities needed to +make a total of size bytes. + +If you wish to construct a client to talk to the rgpiod daemon the following +are a good source of information. +[ul] +rpgio.py - a Python client +rgpio.c - a C client +rgs.c - a C command line client +lgCmd.c - a useful summary of the socket commands +[ul] +TEXT*/ + diff --git a/DOC/src/defs/rgs.def b/DOC/src/defs/rgs.def new file mode 100644 index 0000000..56c59e1 --- /dev/null +++ b/DOC/src/defs/rgs.def @@ -0,0 +1,1982 @@ +INTRO + +rgs is a program 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 reading and writing GPIO singly and in groups +o software timed PWM and waves +o GPIO callbacks +o pipe notification of GPIO events +o I2C wrapper +o SPI wrapper +o serial link wrapper +o simple file handling +o creating and running scripts on the rgpiod daemon + +*Usage* + +rgs {command}+ + +rgs will show the result of the command on screen. + +The rgs process returns an exit status (which can be displayed with +the command echo $?). + +... +RGS_OK 0 +RGS_CONNECT_ERR 255 +RGS_OPTION_ERR 254 +RGS_SCRIPT_ERR 253 +... + +If an error was detected a message will have been written to stderr. +This is likely to be more informative than the message returned by rgs. + +Several commands may be entered on a line. If present PROC and PARSE must +be the last command on a line. + +*Notes* + +rgs does not show the status of successful commands unless the +command itself returns data. The status (0) will be returned to +rgs but will be discarded. + +When a command takes a number as a parameter it may be entered as hex +(precede by 0x), octal (precede by 0), or decimal. + +E.g. 23 is 23 decimal, 0x100 is 256 decimal, 070 is 56 decimal. + +Some commands can return a variable number of data bytes. By +default this data is displayed as decimal. The rgs -a option +can be used to force the display as ASCII and the rgs -x +option can be used to force the display as hex. + +E.g. assuming the transmitted serial data is the letters ABCDEONM + +... +$ rgs serr 4 100 # assumes serial data available from handle 4 +8 65 66 67 68 69 79 78 77 + +$ rgs -a serr 4 100 +8 ABCDEONM + +$ rgs -x serr 4 100 +8 41 42 43 44 45 4f 4e 4d +... + +*Permissions* + +Generally objects created on the rgpiod daemon exist for the duration of the +socket connection. + +For a Python script this will be for the duration of the script. For a +program linked with rgpio this will be for the duration of the program. + +For rgs it is the command line. + +This means that the following command will achieve little + +... +rgs go 0 # get handle to gpiochip 0 +... + +The daemon will delete the handle as soon as the rgs command has finished. + +To preserve the handle it must be shared. + +A lot of the examples will show the command c 1 (use share id 1). +This means the handle is preserved and may be used in subsequent commands. + +. . +rgs c 1 go 0 # get and preserve handle to gpiochip 0 +. . + +If a command is privileged it is indicated in the notes for the +command. The examples given here assume the daemon access control +system is not active (so any user can use privileged commands). + +OVERVIEW + +FILES + +FO file mode :: File open +FC h :: File close + +FR h num :: File read +FW h bvs :: File write + +FS h num from :: File seek + +FL pat num :: File list + +GPIO + +GO gc :: gpiochip open device +GC h :: gpiochip close device + +GIC h :: gpiochip information +GIL h g :: gpiochip line information + +GMODE h g :: GPIO get mode + +GSI h g :: GPIO claim for input (simple) +GSIX h lf g :: GPIO claim for input + +GSO h g :: GPIO claim for output (simple) +GSOX h lf g v :: GPIO claim for output + +GSA h g nfyh :: GPIO claim for alerts (simple) +GSAX h lf ef g nfyh :: GPIO claim for alerts + +GSF h g :: GPIO free + +GSGI h g* :: GPIO group claim for inputs (simple) +GSGIX h lf g* :: GPIO group claim for inputs + +GSGO h g* :: GPIO group claim for outputs (simple) +GSGOX h lf g* v* :: GPIO group claim for outputs + +GSGF h g :: GPIO group free + +GR h g :: GPIO read +GW h g v :: GPIO write + +GGR h g :: GPIO group read + +GGW h g gbits :: GPIO group write (simple) +GGWX h g gbits gmask :: GPIO group write + +GP h g mon moff :: GPIO tx pulse (simple) +GPX h g mon moff off cyc :: GPIO tx pulse + +P h g pf pdc :: GPIO tx PWM (simple) +PX h g pf pdc off cyc :: GPIO tx PWM + +S h g spw :: GPIO tx servo pulses (simple) +SX h g spw sf off cyc :: GPIO tx servo pulses + +GWAVE h g p* :: GPIO group tx wave +GBUSY h g k :: GPIO or group tx busy +GROOM h g k :: GPIO or group tx entries + +GDEB h g us :: GPIO debounce time +GWDOG h g us :: GPIO watchdog time + +I2C + +I2CO ib id if :: I2C open device +I2CC h :: I2C close device + +I2CWQ h bit :: SMB Write Quick: write bit + +I2CRS h :: SMB Read Byte: read byte +I2CWS h bv :: SMB Write Byte: write byte + +I2CRB h r :: SMB Read Byte Data: read byte from register +I2CWB h r bv :: SMB Write Byte Data: write byte to register + +I2CRW h r :: SMB Read Word Data: read word from register +I2CWW h r wv :: SMB Write Word Data: write word to register + +I2CRK h r :: SMB Read Block Data: read data from register +I2CWK h r bvs :: SMB Write Block Data: write data to register + +I2CWI h r bvs :: SMB Write I2C Block Data +I2CRI h r num :: SMB Read I2C Block Data: read bytes from register + +I2CRD h num :: I2C read device +I2CWD h bvs :: I2C write device + +I2CPC h r wv :: SMB Process Call: exchange register with word +I2CPK h r bvs :: SMB Block Process Call: exchange data bytes with register + +I2CZ h bvs :: I2C zip + +NOTIFICATIONS + +NO :: Notification open +NC h :: Notification close +NP h :: Notification pause +NR h :: Notification resume + +SCRIPTS + +PROC t :: Script store +PROCR h pars :: Script run +PROCU h pars :: Script update parameters +PROCP h :: Script get status and parameters +PROCS h :: Script stop +PROCD h :: Script delete + +PARSE t :: Script validate + +SERIAL + +SERO dev b sef :: Serial open device +SERC h :: Serial close device + +SERRB :: Serial read byte +SERWB h bv :: Serial write byte + +SERR h num :: Serial read bytes +SERW h bvs :: Serial write bytes + +SERDA h :: Serial data available + +SHELL + +SHELL name str :: Execute a shell command + +SPI + +SPIO spd spc b spf :: SPI open device +SPIC h :: SPI close device + +SPIR h num :: SPI read bytes +SPIW h bvs :: SPI write bytes + +SPIX h bvs :: SPI transfer bytes + +UTILITIES + +LGV :: Get lg library version +SBC :: Get SBC's host name + +CGI cid :: Get internal configuration setting +CSI cid v :: Set internal configuration setting + +T/TICK :: Get nanoseconds since the epoch + +MICS v :: Microseconds delay +MILS v :: Milliseconds delay + +U/USER :: Set user + +C/SHARE :: Set share + +LCFG :: Reload permits configuration file +PCD :: Print daemon configuration directory +PWD :: Print daemon working directory + +COMMANDS + +*FILES* + +FO :: +This is a privileged command. See [+permits+]. + +This function returns a handle to a file opened in a specified mode. + +Upon success a handle (>=0) is returned. On error a negative status code +will be returned. + +The mode may have the following values. + + @ Value @ Meaning +READ @ 1 @ open file for reading +WRITE @ 2 @ open file for writing +RW @ 3 @ open file for reading and writing + +The following values may be or'd into the mode. + + @ Value @ Meaning +APPEND @ 4 @ All writes append data to the end of the file +CREATE @ 8 @ The file is created if it doesn't exist +TRUNC @ 16 @ The file is truncated + +Newly created files are owned by the user that launched the daemon +with permissions owner read and write. + +... +ls /ram/*.c +/ram/q.c /ram/qdhtxx.c /ram/q-errcod.c /ram/q_t1.c +/ram/q-c1.c /ram/Q-err.c /ram/q-group.c /ram/q_t2.c + +$ rgs c 1 fo /ram/q.c 1 # read access +1 + +$ rgs c 1 fo /ram/new.c 1 # file does not exist +-58 +ERROR: file open failed + +$rgs c 1 fo /ram/new.c 9 # can not create file +-67 +ERROR: no permission to access file +... + +FC:: +This command closes a file previously opened by [*FO*]. + +Upon success nothing is returned. On error a negative status code +will be returned. + +... +$ rgs c 1 fc 1 # First close okay. + +$ rgs c 1 fc 1 # Second fails. +-5 +ERROR: unknown handle +... + + +FR:: +This command returns up to [#num#] bytes of data read from the file. + +Upon success the count of returned bytes followed by the bytes themselves +is returned. On error a negative status code will be returned. + +... +$ rgs c 1 fr 0 10 +5 48 49 128 144 255 + +$ rgs c 1 fr 0 10 +0 +... + +FW:: +This command writes [#bvs#] bytes to the file. + +Upon success nothing is returned. On error a negative status code +will be returned. + +... +$ rgs c 1 fw 0 23 45 67 89 +... + +FS :: +This command seeks to a position within the file. + +The number of bytes to move is [#num#]. Positive offsets +move forward, negative offsets backwards. The move start +position is determined by [#from#] as follows. + + @ From +0 @ start +1 @ current position +2 @ end + +Upon success the new byte position within the file (>=0) is +returned. On error a negative status code will be returned. + +... +$ rgs c 1 fs 0 200 0 # Seek to start of file plus 200 +200 + +$ rgs c 1 fs 0 0 1 # Return current position +200 + +$ rgs c 1 fs 0 0 2 # Seek to end of file, return size +296235 +... + +FL :: +This command returns a list of the files matching [#pat#]. Up +to [#num#] bytes may be returned. + +Upon success the count of returned bytes followed by the matching +files is returned. On error a negative status code will be returned. + +A newline (0x0a) character separates each file name. + +This is a privileged command. See [+permits+]. + +... +$ rgs -a fl "/sys/bus/w1/devices/28*/w1_slave" 5000 +90 /sys/bus/w1/devices/28-000005d34cd2/w1_slave +/sys/bus/w1/devices/28-001414abbeff/w1_slave + +$ rgs -a fl "/sys/bus/*" 5000 +ERROR: no permission to access file +-67 +... + +*GPIO* + +GO :: + +This is a privileged command. See [+permits+]. + +This command opens a gpiochip. + +... +$ rgs c 1 go 0 # open /dev/gpiochip0 +1 +$ rgs c 1 go 23 # try to open /dev/gpiochip23 +-78 +ERROR: can not open gpiochip +... + +GC :: + +This command closes a gpiochip previously opened by [*GO*]. + +... +$ rgs c 1 gc 1 # first close ok +$ rgs c 1 gc 1 # already closed +-5 +ERROR: unknown handle +... + +GIC :: + +This command gets information for an opened gpiochip. In particular +it gets the number of GPIO on the gpiochip, its name, and its usage. + +... +$ rgs c 1 gic 1 +54 "gpiochip0" "pinctrl-bcm2835" +... + +GIL :: + +This command gets information for GPIO [#g#] of an opened gpiochip. +In particular it gets the GPIO number, kernel usage flags, its user, +and its purpose. + +The usage flags are bits. + +Bit value @ Bit meaning +1 @ GPIO in use by the kernel +2 @ GPIO is an output +4 @ GPIO is active low +8 @ GPIO is open drain +16 @ GPIO is open source + +The user and purpose fields are filled in by the software which has +claimed the GPIO and may be blank. + +... +$ for ((i=2; i<10; i++)); do rgs c 1 gil 1 $i; done +2 0 "" "" +3 0 "" "" +4 11 "" "onewire.0" +5 0 "" "" +6 0 "" "" +7 7 "" "spi0 CS1" +8 7 "" "spi0 CS0" +9 0 "" "" +... + +GMODE :: + +This command gets the mode for GPIO [#g#] of an opened gpiochip. + +Mode 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: --- +6 @ 64 @ Kernel: --- +7 @ 128 @ Kernel: --- +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: --- + +GSI :: + +This command claims GPIO [#g#] for input. + +... +$ rgs c 1 gsi 1 23 # claim GPIO 23 for input. +... + +GSIX :: + +This command claims GPIO [#g#] for input. + +The line flags [#lf#] may be used to set the GPIO +as active low, open drain, or open source. + +... +$ rgs c 1 gsi 1 0 23 # claim GPIO 23 for input. +... + +GSO :: + +This command claims GPIO [#g#] for output. + + +The GPIO will be initialised low. + +... +$ rgs c 1 gso 1 25 # claim GPIO 25 for low output. +... + +GSOX :: + +This command claims GPIO [#g#] for output. + +The line flags [#lf#] may be used to set the GPIO +as active low, open drain, or open source. + +If [#v#] is zero the GPIO will be initialised low. If any other +value is used the GPIO will be initialised high. + +... +$ rgs c 1 gso 1 0 25 # claim GPIO 25 for high output. +... + +GSA :: + +This command claims GPIO [#g#] for alerts. + +Alerts will be generated for both edges. + +The alerts will be sent to a previously opened notification pipe [#nfyh#]. + +GSAX :: + +This command claims GPIO [#g#] for alerts. + +The line flags [#lf#] may be used to set the GPIO +as active low, open drain, or open source. + +The event flags [#ef#] specify whether alerts should be +generated on a rising edge, falling edge, or both edges. + +The alerts will be sent to a previously opened notification pipe [#nfyh#]. + +GSF :: + +This command releases GPIO [#g#]. The GPIO +may now be claimed by another user or for a different purpose. + +GSGI :: + +This command claims a group of GPIO for inputs. + +[#g*#] 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. + +... +$ rgs c 1 gsgi 1 16 17 18 19 20 21 +... + +GSGIX :: + +This command claims a group of GPIO for inputs. All the GPIO +share the same line flag setting. + +The line flags [#lf#] may be used to set the GPIO +as active low, open drain, or open source. + +[#g*#] 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. + +... +$ rgs c 1 gsgix 1 0 16 17 18 19 20 21 +... + +GSGO :: + +This command claims a group of GPIO for outputs. + +[#g*#] 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. + +The GPIO will be initialised low. + +... +$ rgs c 1 gsgo 1 22 23 24 25 +... + +GSGOX :: + +This command claims a group of GPIO for outputs. All the GPIO +and share the same line flag setting. + +The line flags [#lf#] may be used to set the GPIO +as active low, open drain, or open source. + +[#g*#] 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. + +[#v*#] is a list of initialisation values for the GPIO. If a value is +zero the corresponding GPIO will be initialised low. If any other +value is used the corresponding GPIO will be initialised high. + +... +$ rgs c 1 gsgox 1 0 22 23 24 25 1 1 1 1 +... + +GSGF :: + +This command releases the group of GPIO identified by the group +leader [#g#]. The GPIO +may now be claimed by another user or for a different purpose. + +... +rgs c 1 gsgf 1 22 +... + +GR :: + +This command returns the current value (0 or 1) of GPIO [#g#]. + +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. + +... +$ rgs c 1 gr 1 22 +1 +... + +GW :: + +This command sets the value (0 or 1) of GPIO [#g#]. + +This command will work for any GPIO claimed as an output +(even if a member of a group). + +If [#v#] is zero the GPIO will be set low. +If any other value is used the GPIO will be set high. + +GGR :: + +This command reads a group of GPIO identified by group leader [#g#]. + +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. + +Two values are returned. The first is the group size (the number of +GPIO in the group). The second is the group bits as a decimal value. + +Bit 0 is the level of the group leader. +Bit 1 is the level of the second GPIO in the group. +Bit g is the level of GPIO g+1 in the group. + +... +$ rgs c 1 gsgi 1 0 16 17 18 19 20 21 +$ rgs c 1 ggr 1 16 +6 49 # six GPIO, group leader (16) high, 17-19 low, 20-21 high +... + +GGW :: + +This command writes a group of GPIO identified by group leader [#g#]. + +The values of each GPIO of the group are set according to the bits +of [#gbits#]. + +Bit 0 sets the level of the group leader. +Bit 1 sets the level of the second GPIO in the group. +Bit g sets the level of GPIO g+1 in the group. + +... +$ rgs c 1 ggr 1 22 +4 15 +$ rgs c 1 ggw 1 22 5 +$ rgs c 1 ggr 1 22 +4 5 +$ rgs c 1 ggw 1 22 10 +$ rgs c 1 ggr 1 22 +4 10 +... + +GGWX :: + +This command writes a group of GPIO identified by group leader [#g#]. + +The values of each GPIO of the group are set according to the bits +of [#gbits#]. + +Bit 0 sets the level of the group leader. +Bit 1 sets the level of the second GPIO in the group. +Bit g sets the level of GPIO g+1 in the group. + +However this may be modified by the [#gmask#]. A GPIO is only +updated if the corresponding bit in the mask is 1. + +... +$ rgs c 1 ggr 1 22 +4 15 +$ rgs c 1 ggw 1 22 5 15 +$ rgs c 1 ggr 1 22 +4 5 +$ rgs c 1 ggw 1 22 10 0 +$ rgs c 1 ggr 1 22 +4 5 +$ rgs c 1 ggw 1 22 10 15 +$ rgs c 1 ggr 1 22 +4 10 +... + +GP :: + +This command starts software timed pulses on GPIO [#g#] . + +Each cycle consists of [#mon#] microseconds of GPIO high +followed by [#moff#] microseconds of GPIO low. + +PWM is characterised by two values, its frequency (number of cycles +per second) and its duty cycle (percentage of high time per cycle). + +The set frequency will be 1000000 / (mon + moff) Hz. + +The set duty cycle will be mon / (mon + moff) * 100 %. + +E.g. if mon is 50 and moff is 100 the frequency will be 6666.67 Hz +and the duty cycle will be 33.33 %. + +GPX :: + +This command starts software timed pulses on GPIO [#g#] . + +[#cyc#] cycles are transmitted (0 means infinite). Each cycle +consists of [#mon#] microseconds of GPIO high followed by [#moff#] +microseconds of GPIO low. + +PWM is characterised by two values, its frequency (number of cycles +per second) and its duty cycle (percentage of high time per cycle). + +The set frequency will be 1000000 / (mon + moff) Hz. + +The set duty cycle will be mon / (mon + moff) * 100 %. + +E.g. if mon is 50 and moff is 100 the frequency will be 6666.67 Hz +and the duty cycle will be 33.33 %. + +[#off#] is a microsecond offset from the natural start of the PWM 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 command may be issued to the GPIO before the last has finished. + +If the last command had infinite cycles ([#cyc#] 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 [#cyc#] cycles. + +Multiple pulse settings may be queued in this way. + +P:: + +This command starts software timed PWM on GPIO [#g#] . + +PWM is characterised by two values, its frequency (number of cycles +per second) and its duty cycle (percentage of high time per cycle). + +PX:: + +This command starts software timed PWM on GPIO [#g#] . + +PWM is characterised by two values, its frequency (number of cycles +per second) and its duty cycle (percentage of high time per cycle). + +[#off#] is a microsecond offset from the natural start of the PWM 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 PWM had infinite cycles ([#cyc#] 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 [#cyc#] cycles. + +Multiple PWM settings may be queued in this way. + +S :: + +This command starts software timed servo pulses on GPIO [#g#] . + +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. + +SX :: + +This command starts software timed servo pulses on GPIO [#g#] . + +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. + +Another servo command may be issued to the GPIO before the last has finished. + +If the last command had infinite cycles ([#cyc#] 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 [#cyc#] cycles. + +Multiple servo settings may be queued in this way. + +GWAVE :: + +This command starts a wave on GPIO group [#g#] . + +[#p#] is a series of pulses to be transmitted on the GPIO group. + +Each pulse is defined by the following triplet: + +[#gbits#] the levels to set for the selected GPIO +[#gmask#] the GPIO to select +[#us#] the delay in microseconds before the next pulse + +Another wave command may be issued to the GPIO group before the +last has finished transmission. + +Multiple waves may be queued in this way. + +GBUSY :: + +This command checks to see if a specified kind [#k#] of transmission +is ongoing on a GPIO or GPIO group [#g#] . + +The command returns 1 if transmission is ongoing, otherwise 0. + +GROOM :: + +This returns the number of slots there are to queue further +transmissions of a specified kind [#k#] in the tx queue for +GPIO or GPIO group [#g#]. + +The command returns the number of free slots (0 for no free slots). + +GDEB :: + +This command sets the debounce time for GPIO [#g#] to [#us#] microseconds. + +This command is only effective when the GPIO is being used as +a source of alerts. + +Any level changes shorter than the debounce setting will be +discarded, i.e. they will not generate an alert. + +Reported level changes will be timestamped [#us#] microseconds +after the level change. + +GWDOG :: + +This command sets the watchdog time for GPIO [#g#] + to [#us#] microseconds. + +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 2 for a watchdog alert. + +*I2C* + +I2CO :: + +This is a privileged command. See [+permits+]. + +This command returns a handle to access device [#id#] on I2C bus [#ib#]. +The device is opened with flags [#if#]. + +No flags are currently defined. The parameter [#if#] should be 0. + +Upon success the next free handle (>=0) is returned. On error a +negative status code will be returned. + +... +$ rgs c 1 i2co 1 0x70 0 # Bus 1, device 0x70, flags 0. +0 + +$ rgs c 1 i2co 1 0x53 0 # Bus 1, device 0x53, flags 0. +1 +... + +I2CC :: +This command closes an I2C device previously opened by [*I2CO*]. + +Upon success nothing is returned. On error a negative status code +will be returned. + +... +$ rgs c 1 i2cc 0 # First close okay. + +$ rgs c 1 i2cc 0 # Second fails. +-25 +ERROR: unknown handle +... + +I2CWQ :: + +This command writes a single [#bit#] to the I2C device. + +Upon success nothing is returned. On error a negative status code +will be returned. + +... +$ rgs c 1 i2cwq 0 1 +... + +I2CRS :: + +This command returns a single byte read from the I2C device. + +Upon success a value between 0 and 255 will be returned. On error +a negative status code will be returned. + +... +$ rgs c 1 i2crs 0 +0 +... + +I2CWS :: + +This command writes a single byte [#bv#] to the I2C device. + +Upon success nothing is returned. On error a negative status code +will be returned. + +... +$ rgs c 1 i2cws 0 0x12 + +$ rgs c 1 i2cws 0 0xff +-82 +ERROR: I2C write failed +... + +I2CRB :: + +This command returns a single byte read from register [#r#] of +the I2C device. + +Upon success a value between 0 and 255 will be returned. On error +a negative status code will be returned. + +... +$ rgs c 1 i2crb 0 0 +6 +... + +I2CWB :: + +This command writes a single byte [#bv#] to register [#r#] of the +I2C device. + +Upon success nothing is returned. On error a negative status code +will be returned. + +... +$ rgs c 1 i2cwb 0 10 0x54 +... + +I2CRW :: + +This command returns a single 16 bit word read from register [#r#] of +the I2C device. + +Upon success a value between 0 and 65535 will be returned. On error +a negative status code will be returned. + +... +$ rgs c 1 i2crw 0 0 +6150 +... + +I2CWW :: + +This command writes a single 16 bit word [#wv#] to register [#r#] of +the I2C device. + +Upon success nothing is returned. On error a negative status code +will be returned. + +... +$ rgs c 1 i2cww 0 0 0xffff +... + +I2CRK :: + +This command returns between 1 and 32 bytes read from register [#r#] of +the I2C device. + +Upon success the count of returned bytes followed by the bytes themselves +is returned. On error a negative status code will be returned. + +The number of bytes of returned data is specific to the device and +register. + +... +$ rgs c 1 i2crk 0 0 +6 0 0 0 0 0 0 + +$ rgs c 1 i2crk 0 1 +24 0 0 0 0 0 0 0 0 0 0 0 0 120 222 105 215 128 87 195 217 0 0 0 0 +... + +I2CWK :: + +This command writes between 1 and 32 bytes [#bvs#] to register [#r#] of +the I2C device. + +Upon success nothing is returned. On error a negative status code +will be returned. + +... +rgs c 1 i2cwk 0 4 0x01 0x04 0xc0 +... + +I2CRI :: + +This command returns [#num#] bytes from register [#r#] of +the I2C device. + +Upon success the count of returned bytes followed by the bytes themselves +is returned. On error a negative status code will be returned. + +The parameter [#num#] may be 1-32. + +... +$ rgs c 1 i2cri 0 0 16 +16 237 155 155 155 155 155 155 155 155 155 155 155 155 155 155 155 +... + +I2CWI :: + +This command writes between 1 and 32 bytes [#bvs#] to register [#r#] of +the I2C device. + +Upon success nothing is returned. On error a negative status code +will be returned. + +... +$ rgs c 1 i2cwi 0 4 0x01 0x04 0xc0 +... + +I2CRD :: + +This command returns [#num#] bytes read from the I2C device. + +Upon success the count of returned bytes followed by the bytes themselves +is returned. On error a negative status code will be returned. + +This command operates on the raw I2C device. The maximum value of the +parameter [#num#] is dependent on the I2C drivers and the device +itself. rgs imposes a limit of about 8000 bytes. + +... +$ rgs c 1 i2crd 0 16 +16 6 24 0 0 0 0 0 0 0 0 0 0 0 0 32 78 +... + +I2CWD :: + +This command writes a block of bytes [#bvs#] to the I2C device. + +Upon success nothing is returned. On error a negative status code +will be returned. + +The number of bytes which may be written in one transaction is +dependent on the I2C drivers and the device itself. rgs imposes +a limit of about 500 bytes. + +This command operates on the raw I2C device. + +... +$ rgs c 1 i2cwd 0 0x01 0x02 0x03 0x04 +... + +I2CPC :: +This command writes [#wv#] to register [#r#] of the I2C device +and returns a 16-bit word read from the device. + +Upon success a value between 0 and 65535 will be returned. On error +a negative status code will be returned. + +... +$ rgs c 1 i2cpc 0 37 43210 +39933 + +$ rgs c 1 i2cpc 0 256 43210 +ERROR: bad i2c/spi/ser parameter +-81 +... + +I2CPK :: + +This command writes the data bytes [#bvs#] to register [#r#] of +the I2C device and returns a device specific number of bytes. + +Upon success the count of returned bytes followed by the bytes themselves +is returned. On error a negative status code will be returned. + +... +$ rgs c 1 i2cpk 0 0 0x11 0x12 +6 0 0 0 0 0 0 +... + +I2CZ :: +This command executes a sequence of I2C operations. The +operations to be performed are specified by the contents of [#bvs#] +which contains the concatenated command codes and associated data. + +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 [#h#]. +The flags default to 0. The address and flags maintain their +previous value until updated. + +... +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 +... + +*NOTIFICATIONS* + +NO :: + +This is a privileged command. See [+permits+]. + +This command requests a free notification handle. + +A notification is a method for being notified of GPIO state changes via a pipe. + +Upon success the command returns a handle greater than or equal to zero. +On error a negative status code will be returned. + +The pipes are created in the daemon's working directory (the command +[#pwd#] will show the working directory). + +Notifications for handle g will be available at the pipe named lgd-nfyx +(where g is the handle number). + +E.g. if the command returns 15 then the notifications must be read +from lgd-nfy15. + +... +$ rgs c 1 no +0 +... + +NC :: + +This command closes a notification previously opened by [*NO*]. + +Upon success nothing is returned. On error a negative status code +will be returned. + +... +$ rgs c 1 nc 0 # First call succeeds. + +$ rgs c 1 nc 1 # Second call fails. +-5 +ERROR: unknown handle +... + +NP :: + +This command pauses notifications. + +Upon success nothing is returned. On error a negative status code +will be returned. + +Notifications for the handle are paused until a [#NR#] command. + +... +$ rgs c 1 np 0 +... + +NR :: + +This command resumes notifications. + +Upon success nothing is returned. On error a negative status code +will be returned. + +... +$ rgs c 1 nr 0 + +$ rgs c 1 nr 1 +-5 +ERROR: unknown handle +... + +*SCRIPTS* + +PROC :: + +This is a privileged command. See [+permits+]. + +This command stores a script [#t#] for later execution. + +If the script is valid a handle (>=0) is returned which is passed +to the other script commands. On error a negative status code +will be returned. + +... +$ rgs proc tag 123 w 4 0 mils 200 w 4 1 mils 300 dcr p0 jp 123 +0 + +$ rgs proc tag 123 w 4 0 mils 5 w 4 1 mils 5 jmp 12 +ERROR: script has unresolved tag +-63 +... + +PROCR :: + +This command runs stored script [#h#] passing it up to 10 optional +parameters. + +Upon success nothing is returned. On error a negative status code +will be returned. + +... +$ rgs proc tag 123 w 4 0 mils 200 w 4 1 mils 300 dcr p0 jp 123 +0 + +$ rgs procr 0 50 # Run script 0 with parameter 0 of 50. + +$ rgs procp 0 +2 44 0 0 0 0 0 0 0 0 0 +$ rgs procp 0 +2 37 0 0 0 0 0 0 0 0 0 +$ rgs procp 0 +2 10 0 0 0 0 0 0 0 0 0 +$ rgs procp 0 +2 5 0 0 0 0 0 0 0 0 0 +$ rgs procp 0 +2 2 0 0 0 0 0 0 0 0 0 +$ rgs procp 0 +1 -1 0 0 0 0 0 0 0 0 0 +... + +PROCU :: + +This command sets the parameters of a stored script [#h#] passing +it up to 10 parameters. + +Upon success nothing is returned. On error a negative status code +will be returned. + +... +$ rgs proc tag 0 hp 18 p0 p1 mils 1000 jmp 0 +0 +$ rgs procu 0 50 500000 +$ rgs procr 0 +$ rgs procu 0 100 +$ rgs procu 0 200 +$ rgs procu 0 200 100000 +... + +PROCP :: + +This command returns the status of script [#h#] as well as the +current value of its 10 parameters. + +Upon success the script status and parameters are returned. +On error a negative status code will be returned. + +The script status may be one of + +0 @ being initialised +1 @ ready +2 @ running +3 @ waiting +4 @ ended +5 @ halted +6 @ failed + +... +$ rgs procp 0 +1 0 0 0 0 0 0 0 0 0 0 +... + +PROCS :: + +This command stops a running script [#h#]. + +Upon success nothing is returned. On error a negative status code +will be returned. + +... +$ rgs procs 0 + +$ rgs procs 1 +-5 +ERROR: unknown handle +... + +PROCD :: + +This command deletes script [#h#]. + +Upon success nothing is returned. On error a negative status code +will be returned. + +... +$ rgs procd 1 + +$ rgs procd 1 +ERROR: unknown handle +-5 +... + +PARSE :: + +Validates the text [#t#] of a script without storing the script. + +Upon success nothing is returned. On error a list of detected +script errors will be given. + +This command may be used to find script syntax faults. + +... +$ rgs parse tag 100 w 22 1 mils 200 w 22 0 mils 800 jmp 100 + +$ rgs parse tag 0 w 22 1 mills 50 w 22 0 dcr p10 jp 99 +Unknown command: mills +Unknown command: 50 +Bad parameter to dcr +Can't resolve tag 99 +... + +*SERIAL* + +SERO:: + +This is a privileged command. See [+permits+]. + +This command opens the serial [#dev#] at [#b#] bits per second. + +No flags are currently defined. [#sef#] should be set to zero. + +Upon success a handle (>=0) is returned. On error a negative status code +will be returned. + +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. + +... +$ rgs sero ttyAMA0 9600 0 +0 + +$ rgs sero tty1 38400 0 +1 +... + +SERC :: + +This command closes a serial device previously opened by [*SERO*]. + +Upon success nothing is returned. On error a negative status code +will be returned. + +... +$ rgs serc 0 # First close okay. + +$ rgs serc 0 # Second close gives error. +-25 +ERROR: unknown handle +... + +SERRB:: + +This command returns a byte of data read from the serial +device. + +Upon success a number between 0 and 255 is returned. +On error a negative status code will be returned. + +... +$ rgs serrb 0 +23 +$ rgs serrb 0 +45 +... + +SERWB:: + +This command writes a single byte [#bv#] to the serial device. + +Upon success nothing is returned. On error a negative status code +will be returned. + +... +$ rgs serwb 0 23 +$ rgs serwb 0 0xf0 +... + +SERR:: + +This command returns up to [#num#] bytes of data read from the +serial device. + +Upon success the count of returned bytes followed by the bytes themselves +is returned. On error a negative status code will be returned. + +... +$ rgs serr 0 10 +5 48 49 128 144 255 + +$ rgs serr 0 10 +0 +... + +SERW:: + +This command writes bytes [#bvs#] to the serial device. + +Upon success nothing is returned. On error a negative status code +will be returned. + +... +$ rgs serw 0 23 45 67 89 +... + +SERDA :: + +This command returns the number of bytes of data available +to be read from the serial device. + +Upon success the count of bytes available to be read is +returned (which may be 0). On error a negative status code +will be returned. + +... +$ rgs serda 0 +0 +... + +*SHELL* + +SHELL :: + +This is a privileged command. See [+permits+]. + +This command uses the system call to execute a shell script [#name#] +with the given string [#str#] as its parameter. + +Upon success the exit status of the system call is returned. On error a negative status code will be returned. + +[#name#] 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 +$ rgs shell scr1 hello world +256 + +# pass three parameters, hello, string with spaces, and world +$ rgs shell scr1 "hello 'string with spaces' world" +256 + +# pass one parameter, hello string with spaces world +$ rgs shell scr1 "\"hello string with spaces world\"" +256 + +# non-existent script +$ rgs shell scr78 par1 +32512 +... + +*SPI* + +SPIO :: + +This is a privileged command. See [+permits+]. + +Upon success a handle is returned. On error a negative status code +will be returned. + +Data will be transferred at [#b#] bits per second. The flags [#spf#] +may be used to modify the default behaviour. + +The 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 +. . + + +SPIC :: + +This command closes a SPI device previously opened by [*SPIO*]. + +Upon success nothing is returned. On error a negative status code +will be returned. + +... +$ rgs spic 1 + +$ rgs spic 1 +-25 +ERROR: unknown handle +... + +SPIR :: + +This command returns [#num#] bytes read from the SPI device. + +Upon success the count of returned bytes followed by the bytes themselves +is returned. On error a negative status code will be returned. + +... +$ rgs spir 0 10 # Read 10 bytes from the SPI device. +10 0 0 0 0 0 0 0 0 0 0 +... + +SPIW :: + +This command writes bytes [#bvs#] to the SPI device. + +Upon success nothing is returned. On error a negative status code +will be returned. + +... +$ rgs spiw 0 0x22 0x33 0xcc 0xff +... + +SPIX:: + +This command writes bytes [#bvs#] to the SPI device. + +It returns the same number of bytes read from the device. + +Upon success the count of returned bytes followed by the bytes themselves +is returned. On error a negative status code will be returned. + +... +$ rgs spix 0 0x22 0x33 0xcc 0xff +4 0 0 0 0 +... + +*UTILITIES* + +LGV :: + +This command returns the lg library version. + +... +$ rgs lgv +lg_0.1.0.0 +... + +SBC :: + +This command returns the rgpiod daemon server name. + +... +$ rgs sbc +venus +... + +CGI:: + +This is a privileged command. See [+permits+]. + +This command returns the value of an internal library +configuration setting [#cid#]. + +... +$ rgs cgi 0 +1 +... + +CSI:: + +This is a privileged command. See [+permits+]. + +This command sets the value of the internal library +configuration setting [#cid#] to [#v#]. + +... +$ rgs csi 0 3 +$ rgs cgi 0 +3 +... + +T/TICK :: + +T and TICK are synonyms. + +This command returns the number of nanoseconds since the epoch +(start of 1970). + +... +$ rgs t +1601838936723095901 +$ rgs tick +1601838940792322758 +... + +MICS :: +This command delays execution for [#v#] microseconds. + +Upon success nothing is returned. On error a negative status code +will be returned. + +The main use of this command is expected to be within scripts. + +... +$ rgs mics 20 # Delay 20 microseconds. +$ rgs mics 1000000 # Delay 1 second. +$ rgs mics 5100000 # Delay 5.1 seconds. +-24 +ERROR: bad MICS delay (too large) +... + +MILS :: + +This command delays execution for [#v#] milliseconds. + +Upon success nothing is returned. On error a negative status code +will be returned. + +... +$ rgs mils 2000 # Delay 2 seconds. +$ rgs mils 301000 # Delay 301 seconds. +-25 +ERROR: bad MILS delay (too large) +... + +U/USER :: + +U and USER are synonyms. + +This command sets the current user and associated permissions. + +... +$ rgs u test1 # set user test1 +$ rgs user test1 # set user test1 +$ rgs u testx # unknown user +-95 +ERROR: bad secret for user +... + +C/SHARE :: Set share + +C and SHARE are synonyms. + +This command sets the share for handles. + +The command has two uses. Firstly it sets the share id for any +subsequently created handles on the current command line. Secondly +it sets the share id to use to access any previously created handles +on this or earlier command lines. + +... +rgs c 1 # use share id 1 +rgs share 1 # use share id 1 +rgs c 0 # switch off sharing +rgs share 867 # use share id 867 +... + +LCFG :: + +This is a privileged command. See [+permits+]. + +This command reloads the permits configuration file + +... +$ rgs lcfg +$ rgs lcfg +-93 +ERROR: no permission to perform action +$ rgs lcfg +-93 +ERROR: no permission to perform action +... + +PCD :: + +This command prints the daemon configuration directory + +... +rgs pcd +/home/joan/LG/TEST +... + +PWD :: + +This command prints the daemon working directory + +... +rgs pwd +/home/joan/LG +... + +PARAMETERS + +b :: baud +The command expects the baud rate in bits per second for +the transmission of serial data (I2C/SPI/serial link, waves). + +bit :: bit value (0-1) +The command expects 0 or 1. + +bv :: a byte value (0-255) +The command expects a byte value. + +bvs :: byte values (0-255) +The command expects one or more byte values. + +cid :: +A number identifying an internal configuration item. + +cid @ meaning +0 @ debug level +1 @ minimum transmission period for PWM and waves + +cyc :: >= 0 +The number of PWM pulses to generate. A value of 0 means infinite. + +dev :: a tty serial device +The command expects the name of a serial device without the +leading /dev, e.g. + +... +ttyAMA0 +ttyUSB0 +tty0 +serial0 +... + +ef :: GPIO event flags + +The following values may be or'd to form the event flags. + +Value @ Meaning +1 @ Rising edge +2 @ Falling edge +3 @ Both edges + +file :: a file name +The file name must match an entry in the [files] section of the +permits file. + +from :: 0-2 +Position to seek from [#FS#]. + + @ From +0 @ start +1 @ current position +2 @ end + +g :: GPIO +The command expects a GPIO. + +g* :: +A list of one or more GPIO + +gbits :: +This value is used to set the levels of a GPIO group. + +Bit 0 represents the level of the group leader. +Bit 1 represents the level of the second GPIO in the group. +Bit g represents the level of GPIO g+1 in the group. + +gc :: gpiochip (>=0) +The command expects a gpiochip number. + +gmask :: +This value is used to select GPIO from a GPIO group. + +Bit 0 of the mask indicates item 1 +Bit 1 of the mask indicates item 2 +Bit g of the mask indicates item g+1 + +For example suppose the items are GPIO 5, 10, 23, 25, 11. + +Bit 0 of the mask indicates GPIO 5 +Bit 1 of the mask indicates GPIO 10 +Bit 2 of the mask indicates GPIO 23 +Bit 3 of the mask indicates GPIO 25 +Bit 4 of the mask indicates GPIO 11 + +If a bit of the mask is high the corresponding GPIO will be selected. + +E.g. in the above example if the mask has the value 17 GPIO 5 and GPIO +11 will be selected. + +h :: handle (>=0) +The command expects a handle. + +A handle is a number referencing an object opened by one of [*FO*], +[*I2CO*], [*NO*], [*PROC*], [*SERO*], [*SPIO*], [*GO*]. + +ib :: I2C bus (>=0) +The command expects an I2C bus number. + +id :: I2C device (0-0x7F) +The command expects the address of an I2C device. + +if :: I2C flags (0) +The command expects an I2C flags value. No flags are currently defined. + +k :: +A kind of transmission. + +0 = PWM +1 = WAVE + +lf :: GPIO line flags + +The following values may be or'd to form the line flags. + +Value @ Meaning +4 @ Active low +8 @ Open drain +16 @ Open source + +mode :: lgFile open mode +One of the following values. + + @ Value @ Meaning +READ @ 1 @ open file for reading +WRITE @ 2 @ open file for writing +RW @ 3 @ open file for reading and writing + +The following values can be or'd into the mode. + + @ Value @ Meaning +APPEND @ 4 @ All writes append data to the end of the file +CREATE @ 8 @ The file is created if it doesn't exist +TRUNC @ 16 @ The file is truncated + +moff :: >= 0 +The off period for a PWM pulse in microseconds. + +mon :: >= 0 +The on period for a PWM pulse in microseconds. + +name :: the name of a script + +Only alphanumeric characters, '-' and '_' are allowed in the name. + +nfyh :: >= 0 + +This associates a notification with a GPIO event. + +num :: maximum number of bytes to return (1-) +The command expects the maximum number of bytes to return. + +For the I2C and SPI commands the requested number of bytes will always +be returned. + +For the serial and file commands the smaller of the number of +bytes available to be read (which may be zero) and [#num#] bytes +will be returned. + +off :: >= 0 + +The offset in microseconds from the nominal PWM pulse start. + +p* :: +One or more triplets of [#gbits#], [#gmask#], and [#us#] +microsecond delay. + +pars :: script parameters +The command expects 0 to 10 numbers as parameters to be passed to the script. + +pat :: a file name pattern +A file path which may contain wildcards. To be accessible the path +must match an entry in the [files] section of the permits file. + +pdc :: thousandths of % +PWM duty cycle between 0 % (0) and 100 % (100000). + +pf :: thousandths of Hz +PWM frequency between 0.1 Hz (100) and 10000 Hz (10000000). +Use 0 for off. + +r :: register (0-255) +The command expects an I2C register number. + +sef :: serial flags (32 bits) +The command expects a flag value. No serial flags are currently defined. + +sf :: Hz (40-500) +Servo frequency + +spc :: SPI channel (>= 0) +The command expects a SPI channel. + +spd :: SPI device (>= 0) +The command expects a SPO device. + +spf :: SPI flags +See [*SPIO*]. + +spw :: 0=off, 500-2500 microseconds +Servo pulse width + +str :: a string +The command expects a string. + +t :: a string +The command expects a string. + +us :: +The command expects a time interval measured in microseconds. + +v :: value +The command expects a number. + +v* :: +A list of one or more values. + +wv :: word value (0-65535) +The command expects a word value. + diff --git a/DOC/src/defs/scripts.def b/DOC/src/defs/scripts.def new file mode 100644 index 0000000..fc80aed --- /dev/null +++ b/DOC/src/defs/scripts.def @@ -0,0 +1,97 @@ + +/*TEXT Scripts + +Scripts are programs to be stored and executed by the rgpiod daemon. +They are intended to mitigate any performance problems associated with +the daemon server/client model. + +Scripts are work in progress. + +*Virtual machine* + +A script runs within a virtual machine with + +a 32 bit accumulator A. +a flags register F. +a program counter PC. + +Each script has + +10 parameters named 0 through 9. +150 variables named 0 through 149. +50 labels which are named by any unique number. + +*Commands* + +Many lg commands may be used within a script. However +some commands do not work within the script model as designed and +are not permitted. + +The following commands are not permitted within a script: + +File - FL FO FR FW + +I2C - I2CPK I2CRD I2CRI I2CRK I2CWD I2CWI I2CWK I2CZ + +Script control - PARSE PROC PROCD PROCP PROCR PROCS PROCU + +Serial - SERO SERR SERW SLR + +SPI - SPIR SPIW SPIX + +The following commands are only permitted within a script: + +Command @ Description @ Definition +ADD x @ Add x to accumulator @ A+=x; F=A +AND x @ And x with accumulator @ A&=x; F=A +CALL L @ Call subroutine at tag L @ push(PC+1); PC=L +CMP x @ Compare x with accumulator @ F=A-x +DCR y @ Decrement register @ --*y; F=*y +DCRA @ Decrement accumulator @ --A; F=A +DIV x @ Divide x into accumulator @ A/=x; F=A +HALT @ Halt @ Halt +INR y @ Increment register @ ++*y; F=*y +INRA @ Increment accumulator @ ++A; F=A +JGE L @ Jump if >= 0 to tag L @ if (F>=0) PC=L +JGT L @ Jump if > 0 to tag L @ if (F>0) PC=L +JLE L @ Jump if <= 0 to tag L @ if (F<=0) PC=L +JLT L @ Jump if < 0 to tag L @ if (F<0) PC=L +JMP L @ Jump to tag L @ PC=L +JNZ L @ Jump if non-zero to tag L @ if (F) PC=L +JZ L @ Jump if zero to tag L @ if (!F) PC=L +LD y x @ Load register with x @ *y=x +LDA x @ Load accumulator with x @ A=x +MLT x @ Multiply x with accumulator @ A*=x; F=A +MOD x @ Modulus x with accumulator @ A%=x; F=A +OR x @ Or x with accumulator @ A|=x; F=A +POP y @ Pop register @ y=pop() +POPA @ Pop accumulator @ A=pop() +PUSH y @ Push register @ push(y) +PUSHA @ Push accumulator @ push(A) +RET @ Return from subroutine @ PC=pop() +RL y x @ Rotate left register x bits @ *y<<=x; F=*y +RLA x @ Rotate left accumulator x bits @ A<<=x; F=A +RR y x @ Rotate right register x bits @ *y>>=x; F=*y +RRA x @ Rotate right accumulator x bits @ A>>=x; F=A +SHL y x @ Shift left register x bits @ *y<<=x; F=*y +SHLA x @ Shift left accumulator x bits @ A<<=x; F=A +SHR y x @ Shift right register x bits @ *y>>=x; F=*y +SHRA x @ Shift right accumulator x bits @ A>>=x; F=A +STA y @ Store accumulator in register @ y=A +SUB x @ Subtract x from accumulator @ A-=x; F=A +SYS str @ Run external script @ system(str); F=A +TAG L @ Label the current position @ N/A +X y1 y2 @ Exchange registers y1 and y2 @ t=*y1;*y1=*y2;*y2=t +XA y @ Exchange accumulator and register @ t=A;A=*y;*y=t +XOR x @ Xor x with accumulator @ A^=x; F=A + +x may be a constant, a parameter (p0-p9), or a variable (v0-v149). + +y may be a parameter (p0-p9), or a variable (v0-v149). If p or v isn't +specified y is assumed to be a variable. + +The SYS script receives two unsigned parameters: the accumulator A and +the current GPIO levels. + +TEXT*/ + diff --git a/EXAMPLES/lgpio/bench.c b/EXAMPLES/lgpio/bench.c new file mode 100644 index 0000000..3b20358 --- /dev/null +++ b/EXAMPLES/lgpio/bench.c @@ -0,0 +1,40 @@ +#include +#include + +#include + +#define LFLAGS 0 + +#define OUT 21 +#define LOOPS 20000 + +int main(int argc, char *argv[]) +{ + int h; + int i; + double t0, t1; + + h = lgGpiochipOpen(0); + + if (h >= 0) + { + if (lgGpioClaimOutput(h, LFLAGS, OUT, 0) == LG_OKAY) + { + + t0 = lguTime(); + + for (i=0; i + +#include + +int main(int argc, char *argv[]) +{ + int h; + int i; + lgChipInfo_t cinf; + lgLineInfo_t linf; + + h = lgGpiochipOpen(0); + if (h >= 0) + { + if (lgGpioGetChipInfo(h, &cinf) == LG_OKAY) + { + printf("%d \"%s\" \"%s\"\n", cinf.lines, cinf.name, cinf.label); + + for (i=0; i +#include +#include +#include +#include + +#include + +#define DHTAUTO 0 +#define DHT11 1 +#define DHTXX 2 + +#define DHT_GOOD 0 +#define DHT_BAD_CHECKSUM 1 +#define DHT_BAD_DATA 2 +#define DHT_TIMEOUT 3 + +/* +gcc -o dhtxx dhtxx.c -llg +*/ + +#define MAX_GPIO 32 + +static int decode_dhtxx(uint64_t reading, int model, float *rh, float *temp) +{ +/* + +-------+-------+ + | DHT11 | DHTXX | + +-------+-------+ +Temp C| 0-50 |-40-125| + +-------+-------+ +RH% | 20-80 | 0-100 | + +-------+-------+ + + 0 1 2 3 4 + +------+------+------+------+------+ +DHT11 |check-| 0 | temp | 0 | RH% | + |sum | | | | | + +------+------+------+------+------+ +DHT21 |check-| temp | temp | RH% | RH% | +DHT22 |sum | LSB | MSB | LSB | MSB | +DHT33 | | | | | | +DHT44 | | | | | | + +------+------+------+------+------+ + +*/ + uint8_t byte[5]; + uint8_t chksum; + float div; + float t, h; + int valid; + int status; + + byte[0] = (reading ) & 255; + byte[1] = (reading>> 8) & 255; + byte[2] = (reading>>16) & 255; + byte[3] = (reading>>24) & 255; + byte[4] = (reading>>32) & 255; + + chksum = (byte[1] + byte[2] + byte[3] + byte[4]) & 0xFF; + + valid = 0; + + if (chksum == byte[0]) + { + if (model == DHT11) + { + if ((byte[1] == 0) && (byte[3] == 0)) + { + valid = 1; + + t = byte[2]; + + if (t > 60.0) valid = 0; + + h = byte[4]; + + if ((h < 10.0) || (h > 90.0)) valid = 0; + } + } + else if (model == DHTXX) + { + valid = 1; + + h = ((float)((byte[4]<<8) + byte[3]))/10.0; + + if (h > 110.0) valid = 0; + + if (byte[2] & 128) div = -10.0; else div = 10.0; + + t = ((float)(((byte[2]&127)<<8) + byte[1])) / div; + + if ((t < -50.0) || (t > 135.0)) valid = 0; + } + else /* AUTO */ + { + valid = 1; + + /* Try DHTXX first. */ + + h = ((float)((byte[4]<<8) + byte[3]))/10.0; + + if (h > 110.0) valid = 0; + + if (byte[2] & 128) div = -10.0; else div = 10.0; + + t = ((float)(((byte[2]&127)<<8) + byte[1])) / div; + + if ((t < -50.0) || (t > 135.0)) valid = 0; + + if (!valid) + { + /* If not DHTXX try DHT11. */ + + if ((byte[1] == 0) && (byte[3] == 0)) + { + valid = 1; + + t = byte[2]; + + if (t > 60.0) valid = 0; + + h = byte[4]; + + if ((h < 10.0) || (h > 90.0)) valid = 0; + } + } + } + + if (valid) + { + status = DHT_GOOD; + + *rh = h; + *temp = t; + } + else status = DHT_BAD_DATA; + } + else status = DHT_BAD_CHECKSUM; + + return status; +} + +static int status; +static float h=0, t=0; + +void afunc(int e, lgGpioAlert_p evt, void *data) +{ + int i; + uint64_t edge_len, now_tick; + static int bits = 0; + static uint64_t reading = 0; + static uint64_t last_tick = 0; + + for (i=0; i 1e6) // a millisecond + { + reading = 0; + bits = 0; + } + else + { + reading <<= 1; + if (edge_len > 1e5) reading |= 1; // longer than 100 micros + ++bits; + } + } + else + { + status = decode_dhtxx(reading, DHTAUTO, &t, &h); + reading = 0; + bits = 0; + } + } +} + +int main(int argc, char *argv[]) +{ + int i, g; + int v; + int loop; + int fd; + int chip; + int num_gpio; + int gpio[MAX_GPIO]; + int err; + int count; + + chip = lgGpiochipOpen(0); + + if (argc > 1) + { + num_gpio=argc-1; + if (num_gpio > MAX_GPIO) num_gpio = MAX_GPIO; + for (g=0; g= 0) + { + lgGpioSetUser(chip, "niagra"); + lgGpioSetSamplesFunc(afunc, (void*)123456); + + for (g=0; g +#include + +#include + +#define OUT 21 +#define LOOPS 120 + +#define LFLAGS 0 + +int main(int argc, char *argv[]) +{ + int h; + int i; + double start, end; + + h = lgGpiochipOpen(0); + + if (h >= 0) + { + if (lgGpioClaimOutput(h, LFLAGS, OUT, 0) == LG_OKAY) + { + lgTxPulse(h, OUT, 20000, 30000, 0, 0); + + lguSleep(2); + + lgTxPulse(h, OUT, 20000, 5000, 0, LOOPS); + + start = lguTime(); + + while (lgTxBusy(h, OUT, LG_TX_PWM)) lguSleep(0.01); + + end = lguTime(); + + printf("%d cycles at 40 Hz took %.1f seconds\n", LOOPS, end-start); + } + + lgGpiochipClose(h); + } +} + diff --git a/EXAMPLES/lgpio/tx_wave.c b/EXAMPLES/lgpio/tx_wave.c new file mode 100644 index 0000000..8bd54e1 --- /dev/null +++ b/EXAMPLES/lgpio/tx_wave.c @@ -0,0 +1,54 @@ +#include +#include + +#include + +int OUT[6]={20, 21, 22, 23, 24, 25}; +int lvl[6]={ 0, 0, 0, 0, 0, 0}; + +#define PULSES 500 + +#define LFLAGS 0 + +lgPulse_t pulses[PULSES]; + +int main(int argc, char *argv[]) +{ + int h; + int i; + double start, end; + int delay = 1000; + int total = 0; + + for (i=0; i= 0) + { + if (lgGroupClaimOutput(h, LFLAGS, 6, OUT, lvl) == LG_OKAY) + { + lgTxWave(h, OUT[0], PULSES, pulses); + + start = lguTime(); + + while (lgTxBusy(h, OUT[0], LG_TX_WAVE)) lguSleep(0.01); + + end = lguTime(); + + printf("%d pulses took %.1f seconds (exp=%.1f)\n", + PULSES, end-start, total/1e6); + } + + lgGpiochipClose(h); + } +} + diff --git a/EXAMPLES/py_lgpio/DHT.py b/EXAMPLES/py_lgpio/DHT.py new file mode 100755 index 0000000..8196a26 --- /dev/null +++ b/EXAMPLES/py_lgpio/DHT.py @@ -0,0 +1,227 @@ +#!/usr/bin/env python + +# DHT.py +# 2020-10-16 +# Public Domain + +import time +import lgpio as sbc + +DHTAUTO=0 +DHT11=1 +DHTXX=2 + +DHT_GOOD=0 +DHT_BAD_CHECKSUM=1 +DHT_BAD_DATA=2 +DHT_TIMEOUT=3 + +class sensor: + """ + A class to read the DHTXX temperature/humidity sensors. + """ + def __init__(self, chip, gpio, model=DHTAUTO, callback=None): + """ + Instantiate with the gpiochip, and the GPIO connected + to the DHT temperature and humidity sensor. + + Optionally the model of DHT may be specified. It may be one + of DHT11, DHTXX, or DHTAUTO. It defaults to DHTAUTO in which + case the model of DHT is automtically determined. + + Optionally a callback may be specified. If specified the + callback will be called whenever a new reading is available. + + The callback receives a tuple of timestamp, GPIO, status, + temperature, and humidity. + + The timestamp will be the number of seconds since the epoch + (start of 1970). + + The status will be one of: + 0 DHT_GOOD (a good reading) + 1 DHT_BAD_CHECKSUM (receieved data failed checksum check) + 2 DHT_BAD_DATA (data receieved had one or more invalid values) + 3 DHT_TIMEOUT (no response from sensor) + """ + self._chip = chip + self._gpio = gpio + self._model = model + + self._new_data = False + + self._bits = 0 + self._code = 0 + self._last_edge_tick = 0 + + self._timestamp = time.time() + self._status = DHT_TIMEOUT + self._temperature = 0.0 + self._humidity = 0.0 + + sbc.gpio_set_watchdog_micros(chip, gpio, 1000) # watchdog after 1 ms + self._cb = sbc.callback(chip, gpio, sbc.RISING_EDGE, self._rising_edge) + + + def _datum(self): + return ((self._timestamp, self._gpio, self._status, + self._temperature, self._humidity)) + + def _validate_DHT11(self, b1, b2, b3, b4): + t = b2 + h = b4 + if (b1 == 0) and (b3 == 0) and (t <= 60) and (h >= 9) and (h <= 90): + valid = True + else: + valid = False + return (valid, t, h) + + def _validate_DHTXX(self, b1, b2, b3, b4): + if b2 & 128: + div = -10.0 + else: + div = 10.0 + t = float(((b2&127)<<8) + b1) / div + h = float((b4<<8) + b3) / 10.0 + if (h <= 110.0) and (t >= -50.0) and (t <= 135.0): + valid = True + else: + valid = False + return (valid, t, h) + + def _decode_dhtxx(self): + """ + +-------+-------+ + | DHT11 | DHTXX | + +-------+-------+ + Temp C| 0-50 |-40-125| + +-------+-------+ + RH% | 20-80 | 0-100 | + +-------+-------+ + + 0 1 2 3 4 + +------+------+------+------+------+ + DHT11 |check-| 0 | temp | 0 | RH% | + |sum | | | | | + +------+------+------+------+------+ + DHT21 |check-| temp | temp | RH% | RH% | + DHT22 |sum | LSB | MSB | LSB | MSB | + DHT33 | | | | | | + DHT44 | | | | | | + +------+------+------+------+------+ + """ + b0 = self._code & 0xff + b1 = (self._code >> 8) & 0xff + b2 = (self._code >> 16) & 0xff + b3 = (self._code >> 24) & 0xff + b4 = (self._code >> 32) & 0xff + + chksum = (b1 + b2 + b3 + b4) & 0xFF + + if chksum == b0: + if self._model == DHT11: + valid, t, h = self._validate_DHT11(b1, b2, b3, b4) + elif self._model == DHTXX: + valid, t, h = self._validate_DHTXX(b1, b2, b3, b4) + else: # AUTO + # Try DHTXX first. + valid, t, h = self._validate_DHTXX(b1, b2, b3, b4) + if not valid: + # try DHT11. + valid, t, h = self._validate_DHT11(b1, b2, b3, b4) + if valid: + self._timestamp = time.time() + self._temperature = t + self._humidity = h + self._status = DHT_GOOD + else: + self._status = DHT_BAD_DATA + else: + self._status = DHT_BAD_CHECKSUM + self._new_data = True + + def _rising_edge(self, chip, gpio, level, tick): + if level != sbc.TIMEOUT: + edge_len = tick - self._last_edge_tick + self._last_edge_tick = tick + if edge_len > 2e8: # 0.2 seconds + self._bits = 0 + self._code = 0 + else: + self._code <<= 1 + if edge_len > 1e5: # 100 microseconds, so a high bit + self._code |= 1 + self._bits += 1 + else: # watchdog + if self._bits >= 30: + self._decode_dhtxx() + + def _trigger(self): + sbc.gpio_claim_output(self._chip, self._gpio, 0) + if self._model != DHTXX: + time.sleep(0.015) + else: + time.sleep(0.001) + self._bits = 0 + self._code = 0 + sbc.gpio_claim_alert( + self._chip, self._gpio, sbc.RISING_EDGE) + + def cancel(self): + """ + """ + if self._cb is not None: + self._cb.cancel() + self._cb = None + + def read(self): + """ + """ + self._new_data = False + self._status = DHT_TIMEOUT + self._trigger() + for i in range(20): # timeout after 1 seconds. + time.sleep(0.05) + if self._new_data: + break + if not self._new_data: + print("data timeout") + datum = self._datum() + return datum + +if __name__== "__main__": + import sys + import argparse + import lgpio + import DHT # import current module as a class + + ap = argparse.ArgumentParser() + + ap.add_argument("-c", "--gpiochip", help="gpiochip device number", + type=int, default=0) + + ap.add_argument("gpio", nargs="+", type=int) + + args = ap.parse_args() + + chip = sbc.gpiochip_open(args.gpiochip) + + # Instantiate a class for each GPIO + + S = [] + for g in args.gpio: + s = DHT.sensor(chip, g) + S.append(s) # save class + + while True: + try: + for s in S: + d = s.read() + print("{:.3f} g={:2d} s={} t={:3.1f} rh={:3.1f}". + format(d[0], d[1], d[2], d[3], d[4])) + time.sleep(2) + except KeyboardInterrupt: + break + + for s in S: + s.cancel() diff --git a/EXAMPLES/py_lgpio/bench.py b/EXAMPLES/py_lgpio/bench.py new file mode 100755 index 0000000..4faa219 --- /dev/null +++ b/EXAMPLES/py_lgpio/bench.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python + +import time +import lgpio + +OUT=21 +LOOPS=100000 + +h = lgpio.gpiochip_open(0) + +lgpio.gpio_claim_output(h, OUT) + +t0 = time.time() + +for i in range(LOOPS): + lgpio.gpio_write(h, OUT, 0) + lgpio.gpio_write(h, OUT, 1) + +t1 = time.time() + +lgpio.gpiochip_close(h) + +print("{:.0f} toggles per second".format(LOOPS/(t1-t0))) + diff --git a/EXAMPLES/py_lgpio/chipline.py b/EXAMPLES/py_lgpio/chipline.py new file mode 100755 index 0000000..81381e9 --- /dev/null +++ b/EXAMPLES/py_lgpio/chipline.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python + +import lgpio as sbc + +h = sbc.gpiochip_open(0) + +ci = sbc.gpio_get_chip_info(h) + +print("lines={} name={} label={}".format(ci[1], ci[2], ci[3])) + +for i in range(ci[1]): + li = sbc.gpio_get_line_info(h, i) + print("offset={} flags={} name={} user={}".format(li[1], li[2], li[3], li[4])) + +sbc.gpiochip_close(h) + diff --git a/EXAMPLES/py_lgpio/errors.py b/EXAMPLES/py_lgpio/errors.py new file mode 100755 index 0000000..e255341 --- /dev/null +++ b/EXAMPLES/py_lgpio/errors.py @@ -0,0 +1,359 @@ +#!/usr/bin/env python + +import time +import lgpio as sbc + +def check(func, exp, got): + if exp != got: + print("FAIL: {} (expected {}, got {})".format(func, exp, got)) + else: + print("PASS: {}".format(func)) + +def check_no_error(func, got): + if got < 0: + print("FAIL: {} (expected no error, got {})".format(func, got)) + else: + print("PASS: {} ({})".format(func, got)) + +def check_not_null(func, got): + if got == "": + print("FAIL: {} (expected name, got {})".format(func, got)) + else: + print("PASS: {} ({})".format(func, got)) + +pulses=[] +pulses.append(sbc.pulse(0xff, 0xff, 1000)) +pulses.append(sbc.pulse(0x00, 0x0f, 2000)) +pulses.append(sbc.pulse(0xff, 0xf0, 3000)) +pulses.append(sbc.pulse(0x00, 0xff, 4000)) +pulses.append(sbc.pulse(0xff, 0x55, 5000)) +pulses.append(sbc.pulse(0x00, 0xAA, 6000)) + +sbc.exceptions = False + +status = sbc.set_internal(0, 2) + +status, value = sbc.get_internal(0) +check("get_internal", 3, value) + +status = sbc.set_internal(0, 3) +check("set_internal", sbc.OKAY, status) + +status = sbc.i2c_close(9999) +check("i2c_close", sbc.BAD_HANDLE, status) + +status = sbc.i2c_open(1, 8888, 7777) +check("i2c_open 1", sbc.BAD_I2C_ADDR, status) + +status = sbc.i2c_open(1, 0x40, 7777) +check("i2c_open 2", sbc.BAD_I2C_FLAGS, status) + +status = sbc.i2c_open(999, 0x40, 0) +check("i2c_open 3", sbc.BAD_I2C_BUS, status) + +status = sbc.i2c_process_call(9999, 8888, 7777) +check("i2c_process_call 1", sbc.BAD_I2C_PARAM, status) + +status = sbc.i2c_process_call(9999, 5, 77777) +check("i2c_process_call 2", sbc.BAD_I2C_PARAM, status) + +status = sbc.i2c_process_call(9999, 0xff, 0xffff) +check("i2c_process_call 3", sbc.BAD_HANDLE, status) + +status, dummy = sbc.i2c_block_process_call(9999, 256, [77, 66, 55, 44, 33, 22, 11]) +check("i2c_block_process_call 1", sbc.BAD_I2C_PARAM, status) + +status, dummy = sbc.i2c_block_process_call(9999, 23, []) +check("i2c_block_process_call 2", sbc.BAD_I2C_PARAM, status) + +status, dummy = sbc.i2c_block_process_call(9999, 23, [77, 66, 55, 44, 33, 22, 11]) +check("i2c_block_process_call 3", sbc.BAD_HANDLE, status) + +status = sbc.i2c_read_byte_data(9999, 8888) +check("i2c_read_byte_data 1", sbc.BAD_I2C_PARAM, status) + +status = sbc.i2c_read_byte_data(9999, 250) +check("i2c_read_byte_data 2", sbc.BAD_HANDLE, status) + +status, dummy = sbc.i2c_read_device(9999, 0) +check("i2c_read_device 1", sbc.BAD_I2C_PARAM, status) + +status, dummy = sbc.i2c_read_device(9999, 250) +check("i2c_read_device 2", sbc.BAD_HANDLE, status) + +status, dummy = sbc.i2c_read_i2c_block_data(9999, 8888, 20) +check("i2c_read_i2c_block_data 1", sbc.BAD_I2C_PARAM, status) + +status, dummy = sbc.i2c_read_i2c_block_data(9999, 250, 33) +check("i2c_read_i2c_block_data 2", sbc.BAD_I2C_PARAM, status) + +status, dummy = sbc.i2c_read_i2c_block_data(9999, 250, 30) +check("i2c_read_i2c_block_data 3", sbc.BAD_HANDLE, status) + +status, dummy = sbc.i2c_read_block_data(9999, 8888) +check("i2c_read_block_data 1", sbc.BAD_I2C_PARAM, status) + +status, dummy = sbc.i2c_read_block_data(9999, 25) +check("i2c_read_block_data 2", sbc.BAD_HANDLE, status) + +status = sbc.i2c_read_byte(9999) +check("i2c_read_byte 1", sbc.BAD_HANDLE, status) + +status = sbc.i2c_read_word_data(9999, 8888) +check("i2c_read_word_data 1", sbc.BAD_I2C_PARAM, status) + +status = sbc.i2c_read_word_data(9999, 88) +check("i2c_read_word_data 2", sbc.BAD_HANDLE, status) + +status = sbc.i2c_write_byte_data(9999, 8888, 7777) +check("i2c_write_byte_data 1", sbc.BAD_I2C_PARAM, status) + +status = sbc.i2c_write_byte_data(9999, 8, 777) +check("i2c_write_byte_data 2", sbc.BAD_I2C_PARAM, status) + +status = sbc.i2c_write_byte_data(9999, 8, 77) +check("i2c_write_byte_data 3", sbc.BAD_HANDLE, status) + +status = sbc.i2c_write_device(9999, []) +check("i2c_write_device 1", sbc.BAD_I2C_PARAM, status) + +status = sbc.i2c_write_device(9999, [88, 77, 66, 55, 44, 33, 22, 11]) +check("i2c_write_device 2", sbc.BAD_HANDLE, status) + +status = sbc.i2c_write_i2c_block_data(9999, 8888, [77, 66, 55, 44, 33, 22, 11]) +check("i2c_write_i2c_block_data 1", sbc.BAD_I2C_PARAM, status) + +status = sbc.i2c_write_i2c_block_data(9999, 88, []) +check("i2c_write_i2c_block_data 2", sbc.BAD_I2C_PARAM, status) + +status = sbc.i2c_write_i2c_block_data(9999, 88, [77, 66, 55, 44, 33, 22, 11]) +check("i2c_write_i2c_block_data 3", sbc.BAD_HANDLE, status) + +status = sbc.i2c_write_block_data(9999, 256, [77, 66, 55, 44, 33, 22, 11]) +check("i2c_write_block_data 1", sbc.BAD_I2C_PARAM, status) + +status = sbc.i2c_write_block_data(9999, 55, []) +check("i2c_write_block_data 2", sbc.BAD_I2C_PARAM, status) + +status = sbc.i2c_write_block_data(9999, 55, [77, 66, 55, 44, 33, 22, 11]) +check("i2c_write_block_data 3", sbc.BAD_HANDLE, status) + +status = sbc.i2c_write_quick(9999, 2) +check("i2c_write_quick 1", sbc.BAD_I2C_PARAM, status) + +status = sbc.i2c_write_quick(9999, 1) +check("i2c_write_quick 2", sbc.BAD_HANDLE, status) + +status = sbc.i2c_write_byte(9999, 8888) +check("i2c_write_byte 1", sbc.BAD_I2C_PARAM, status) + +status = sbc.i2c_write_byte(9999, 255) +check("i2c_write_byte 2", sbc.BAD_HANDLE, status) + +status = sbc.i2c_write_word_data(9999, 8888, 7777) +check("i2c_write_word_data 1", sbc.BAD_I2C_PARAM, status) + +status = sbc.i2c_write_word_data(9999, 88, 77777) +check("i2c_write_word_data 2", sbc.BAD_I2C_PARAM, status) + +status = sbc.i2c_write_word_data(9999, 88, 7777) +check("i2c_write_word_data 3", sbc.BAD_HANDLE, status) + +status, dummy = sbc.i2c_zip(9999, []) +check("i2c_zip 1", sbc.BAD_POINTER, status) + +status, dummy = sbc.i2c_zip(9999, [88, 77, 66, 55, 44, 33, 22, 11]) +check("i2c_zip 2", sbc.BAD_HANDLE, status) + +status = sbc.get_module_version() +check_not_null("get_module_version", status) + +h = sbc.notify_open() +check_no_error("notify_open 1", h) + +status = sbc.notify_resume(h) +check("notify_resume 1", sbc.OKAY, status) + +status = sbc.notify_resume(9999) +check("notify_resume 2", sbc.BAD_HANDLE, status) + +status = sbc.notify_pause(h) +check("notify_pause 1", sbc.OKAY, status) + +status = sbc.notify_pause(9999) +check("notify_pause 2", sbc.BAD_HANDLE, status) + +status = sbc.notify_close(h) +check("notify_close 1", sbc.OKAY, status) + +status = sbc.notify_close(9999) +check("notify_close 2", sbc.BAD_HANDLE, status) + +status = sbc.serial_open("raw", 9600, 0) +check("serial_open 1", sbc.SERIAL_OPEN_FAILED, status) + +status = sbc.serial_open("ttyS0", 8888, 7777) +check("serial_open 2", sbc.BAD_SERIAL_SPEED, status) + +status = sbc.serial_open("ttyS0", 9600, 7777) +check("serial_open 3", sbc.BAD_SERIAL_FLAGS, status) + +status = sbc.serial_open("ttyNotALikeyName", 9600, 0) +check("serial_open 4", sbc.SERIAL_OPEN_FAILED, status) + +status = sbc.serial_close(9999) +check("serial_close 1", sbc.BAD_HANDLE, status) + +status = sbc.serial_data_available(9999) +check("serial_data_available 1", sbc.BAD_HANDLE, status) + +status, dummy = sbc.serial_read(9999, 0) +check("serial_read 1", sbc.BAD_SERIAL_PARAM, status) + +status, dummy = sbc.serial_read(9999, 8888) +check("serial_read 2", sbc.BAD_HANDLE, status) + +status = sbc.serial_read_byte(9999) +check("serial_read_byte 1", sbc.BAD_HANDLE, status) + +status = sbc.serial_write(9999, []) +check("serial_write 1", sbc.BAD_SERIAL_PARAM, status) + +status = sbc.serial_write(9999, [88, 77, 66, 55, 44, 33, 22, 11]) +check("serial_write 2", sbc.BAD_HANDLE, status) + +status = sbc.serial_write_byte(9999, 256) +check("serial_write_byte 2", sbc.BAD_SERIAL_PARAM, status) + +status = sbc.serial_write_byte(9999, 88) +check("serial_write_byte 2", sbc.BAD_HANDLE, status) + +status = sbc.spi_close(9999) +check("spi_close 1", sbc.BAD_HANDLE, status) + +status = sbc.spi_open(2, 1, 7777, 6666) +check("spi_open 1", sbc.SPI_OPEN_FAILED, status) + +status, dummy = sbc.spi_read(9999, 0) +check("spi_read 1", sbc.BAD_SPI_COUNT, status) + +status, dummy = sbc.spi_read(9999, 8888) +check("spi_read 2", sbc.BAD_HANDLE, status) + +status = sbc.spi_write(9999, []) +check("spi_write 1", sbc.BAD_SPI_COUNT, status) + +status = sbc.spi_write(9999, [88, 77, 66, 55, 44, 33, 22, 11]) +check("spi_write 2", sbc.BAD_HANDLE, status) + +status, dummy = sbc.spi_xfer(9999, []) +check("spi_xfer 1", sbc.BAD_SPI_COUNT, status) + +status, dummy = sbc.spi_xfer(9999, [88, 77, 66, 55, 44, 33, 22, 11]) +check("spi_xfer 2", sbc.BAD_HANDLE, status) + +status = sbc.gpiochip_close(9999) +check("gpiochip_close 1", sbc.BAD_HANDLE, status) + +status, dummy = sbc.group_read(9999, 8888) +check("group_read 1", sbc.BAD_HANDLE, status) + +status = sbc.group_write(9999, 8888, 7777) +check("group_write 1", sbc.BAD_HANDLE, status) + +status = sbc.gpiochip_open(9999) +check("gpiochip_open 1", sbc.CANNOT_OPEN_CHIP, status) + +status, lines, name, label = sbc.gpio_get_chip_info(9999) +check("gpio_get_chip_info 1", sbc.BAD_HANDLE, status) + +status, offset, flags, name, user = sbc.gpio_get_line_info(9999, 8888) +check("gpio_get_line_info 1", sbc.BAD_HANDLE, status) + +status = sbc.gpio_get_mode(9999, 8888) +check("gpio_get_mode 1", sbc.BAD_HANDLE, status) + +status = sbc.gpio_read(9999, 8888) +check("gpio_read 1", sbc.BAD_HANDLE, status) + +status = sbc.gpio_free(9999, 8888) +check("gpio_free 1", sbc.BAD_HANDLE, status) + +status = sbc.group_free(9999, 8888) +check("group_free 1", sbc.BAD_HANDLE, status) + +status = sbc.tx_pulse(9999, 8888,7777, 6666, 5555, 4444) +check("tx_pulse 1", sbc.BAD_HANDLE, status) + +status = sbc.tx_pwm(9999, 8888, 7777, 6666, 5555, 4444) +check("tx_pwm 1", sbc.BAD_PWM_DUTY, status) + +status = sbc.tx_pwm(9999, 8888, 77777, 50, 5555, 4444) +check("tx_pwm 2", sbc.BAD_PWM_FREQ, status) + +status = sbc.tx_pwm(9999, 8888, 5000, 50, 5555, 4444) +check("tx_pwm 3", sbc.BAD_HANDLE, status) + +status = sbc.tx_servo(9999, 8888, 7777, 6666, 5555, 4444) +check("tx_servo 1", sbc.BAD_SERVO_FREQ, status) + +status = sbc.tx_servo(9999, 8888, 7777, 50, 5555, 4444) +check("tx_servo 2", sbc.BAD_SERVO_WIDTH, status) + +status = sbc.tx_servo(9999, 8888, 1500, 50, 5555, 4444) +check("tx_servo 3", sbc.BAD_HANDLE, status) + +status = sbc.tx_wave(9999, 8888, pulses) +check("tx_wave 1", sbc.BAD_HANDLE, status) + +status = sbc.tx_busy(9999, 8888, 7777) +check("tx_busy 1", sbc.BAD_TX_TYPE, status) + +status = sbc.tx_busy(9999, 8888, 0) +check("tx_busy 2", sbc.BAD_HANDLE, status) + +status = sbc.tx_room(9999, 8888, 7777) +check("tx_room 1", sbc.BAD_TX_TYPE, status) + +status = sbc.tx_room(9999, 8888, 0) +check("tx_room 2", sbc.BAD_HANDLE, status) + +status = sbc.gpio_set_debounce_micros(9999, 8888, 6000000) +check("gpio_set_debounce_micros 1", sbc.BAD_DEBOUNCE_MICS, status) + +status = sbc.gpio_set_debounce_micros(9999, 8888, 600) +check("gpio_set_debounce_micros 2", sbc.BAD_HANDLE, status) + +status = sbc.gpio_set_watchdog_micros(9999, 8888, 400000000) +check("gpio_set_watchdog_micros 1", sbc.BAD_WATCHDOG_MICS, status) + +status = sbc.gpio_set_watchdog_micros(9999, 8888, 4000) +check("gpio_set_watchdog_micros 2", sbc.BAD_HANDLE, status) + +status = sbc.gpio_claim_alert(9999, 8888, 7777, 6666, 5555) +check("gpio_claim_alert 1", sbc.BAD_HANDLE, status) + +status = sbc.gpio_claim_input(9999, 8888) +check("gpio_claim_input 1", sbc.BAD_HANDLE, status) + +status = sbc.group_claim_input(9999, [8888, 7777, 6666]) +check("group_claim_input 1", sbc.BAD_HANDLE, status) + +status = sbc.gpio_claim_output(9999, 8888, 7777) +check("gpio_claim_output 1", sbc.BAD_HANDLE, status) + +status = sbc.group_claim_output(9999, [8888, 7777, 6666]) +check("group_claim_output 1", sbc.BAD_HANDLE, status) + +status = sbc.gpio_write(9999, 8888, 7777) +check("gpio_write 1", sbc.BAD_HANDLE, status) + +err = sbc.error_text(0) +check("error_text 1", "No error", err) + +err = sbc.error_text(-1) +check("error_text 2", "initialisation failed", err) + +err = sbc.error_text(1) +check("error_text 3", "unknown error", err) + diff --git a/EXAMPLES/py_lgpio/q0.py b/EXAMPLES/py_lgpio/q0.py new file mode 100644 index 0000000..0eb6ab5 --- /dev/null +++ b/EXAMPLES/py_lgpio/q0.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python + +import time +import lgpio + +IN=20 +OUT=21 + +h = lgpio.gpiochip_open(0) + +lgpio.gpio_claim_input(h, 0, IN) +lgpio.gpio_claim_output(h, 0, OUT, 1) + +for i in range(1000): + v = lgpio.gpio_read(h, IN) + lgpio.gpio_write(h, OUT, v) + print(v) + time.sleep(0.1) + +lgpio.gpiochip_close(h) + diff --git a/EXAMPLES/py_lgpio/q1.py b/EXAMPLES/py_lgpio/q1.py new file mode 100644 index 0000000..55867eb --- /dev/null +++ b/EXAMPLES/py_lgpio/q1.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python + +import time +import lgpio + +cinfo=lgpio.chip_info() +linfo=lgpio.line_info() + +h = lgpio.gpiochip_open(0) + +lgpio.get_chip_info(h, cinfo) +print(cinfo.lines, cinfo.name, cinfo.label) + +for i in range(cinfo.lines): + lgpio.get_line_info(h, i, linfo) + print(linfo.offset, linfo.lFlags, linfo.name, linfo.user) + +lgpio.gpiochip_close(h) + diff --git a/EXAMPLES/py_lgpio/q2.py b/EXAMPLES/py_lgpio/q2.py new file mode 100644 index 0000000..9016c51 --- /dev/null +++ b/EXAMPLES/py_lgpio/q2.py @@ -0,0 +1,21 @@ +import lgpio +import time +import sys + +def cbf(chip, gpio, value, tick): + print("c={} g={} v={} t={}".format(chip, gpio, value, tick)) + +GPIO=20 + +if len(sys.argv) > 1: + GPIO = int(sys.argv[1]) + +h = lgpio.gpiochip_open(0) + +for i in range(20,28): + lgpio.callback(h, i, lgpio.BOTH_EDGES, cbf) + +lgpio.gpio_claim_alert(h, GPIO, lgpio.BOTH_EDGES) + +time.sleep(600) + diff --git a/EXAMPLES/py_lgpio/testbed.py b/EXAMPLES/py_lgpio/testbed.py new file mode 100755 index 0000000..a51ee42 --- /dev/null +++ b/EXAMPLES/py_lgpio/testbed.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python + +# testbed.py +# 2020-09-20 +# Public Domain + +import time +import lgpio as sbc # http://abyz.me.uk/lg/python.html + +MCP23017_1=0x20 +MCP23017_2=0x21 +NANO=0x2c + +DAC_CS=8 # spidev 0.0 +ADC_CS=25 # spidev 0.1 +NANO_CS=24 # spidev 0.1 + +chip = sbc.gpiochip_open(0) +sbc.gpio_claim_output(chip, 0, ADC_CS, 1) +sbc.gpio_claim_output(chip, 0, NANO_CS, 1) + +dac = sbc.spi_open(0, 0, 50000, 0) +others = sbc.spi_open(0, 1, 50000, 0) + +nano_i2c = sbc.i2c_open(1, NANO, 0) + +nano_serial = sbc.serial_open("serial0", 115200, 0) + +inc = True + +potpos = 0 + +while True: + + while True: + + sbc.spi_write(dac, [0, potpos]) + + for i in range(8): + sbc.gpio_write(chip, ADC_CS, 0) + (b, d) = sbc.spi_xfer(others, [1, 0x80+(i<<4), 0]) + sbc.gpio_write(chip, ADC_CS, 1) + + if b == 3: + c1 = d[1] & 0x03 + c2 = d[2] + ch0 = (c1<<8)+c2 + else: + ch0 = -1 + + print("ADC{}={:4d} pot={}".format(i, ch0, potpos)) + + potpos += 1 + if potpos > 129: + potpos = 0 + + sbc.i2c_write_device(nano_i2c, "Hello world!") + + time.sleep(0.1) + + (b, d) = sbc.serial_read(nano_serial, 1000) + + print(d) + + sbc.serial_write(nano_serial, "0random chars\n9") + + time.sleep(0.2) + + (b, d) = sbc.serial_read(nano_serial, 1000) + + print(d) + + sbc.gpio_write(chip, NANO_CS, 0) + (b, d) = sbc.spi_xfer(others, "A message to SPI\n") + sbc.gpio_write(chip, NANO_CS, 1) + + time.sleep(0.2) + + (b, d) = sbc.serial_read(nano_serial, 1000) + + print(d) + diff --git a/EXAMPLES/py_lgpio/tx_pulse.py b/EXAMPLES/py_lgpio/tx_pulse.py new file mode 100755 index 0000000..2b5298c --- /dev/null +++ b/EXAMPLES/py_lgpio/tx_pulse.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python + +import time +import lgpio as sbc + +OUT=21 +LOOPS=120 + +h = sbc.gpiochip_open(0) + +sbc.gpio_claim_output(h, OUT) + +sbc.tx_pulse(h, OUT, 20000, 30000) # 20 Hz 40 % duty cycle + +time.sleep(2) + +sbc.tx_pulse(h, OUT, 20000, 5000, pulse_cycles=LOOPS) # 40 Hz 80 % + +start = time.time() + +while sbc.tx_busy(h, OUT, sbc.TX_PWM): + time.sleep(0.01) + +end = time.time() + +print("{} cycles at 40 Hz took {:.1f} seconds".format(LOOPS, end-start)) + +sbc.gpiochip_close(h) + diff --git a/EXAMPLES/py_lgpio/tx_wave.py b/EXAMPLES/py_lgpio/tx_wave.py new file mode 100755 index 0000000..b8c8a80 --- /dev/null +++ b/EXAMPLES/py_lgpio/tx_wave.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python + +import time +import lgpio as sbc + +OUT=[20, 21, 22, 23, 24, 25] + +PULSES=500 + +pulses = [] +total = 0 +delay = 1000 + +for i in range(PULSES): + pulses.append(sbc.pulse(i, sbc.GROUP_ALL, delay)) + total += delay + delay += 100 + +h = sbc.gpiochip_open(0) + +sbc.group_claim_output(h, OUT) + +sbc.tx_wave(h, OUT[0], pulses) + +start = time.time() + +while sbc.tx_busy(h, OUT[0], sbc.TX_WAVE): + time.sleep(0.01) + +end = time.time() + +print("{} pulses took {:.1f} seconds (exp={:.1f})". + format(PULSES, end-start, total/1e6)) + +sbc.gpiochip_close(h) + diff --git a/EXAMPLES/py_rgpio/DHT.py b/EXAMPLES/py_rgpio/DHT.py new file mode 100755 index 0000000..e5eca04 --- /dev/null +++ b/EXAMPLES/py_rgpio/DHT.py @@ -0,0 +1,234 @@ +#!/usr/bin/env python + +# DHT.py +# 2020-10-16 +# Public Domain + +import time +import rgpio + +DHTAUTO=0 +DHT11=1 +DHTXX=2 + +DHT_GOOD=0 +DHT_BAD_CHECKSUM=1 +DHT_BAD_DATA=2 +DHT_TIMEOUT=3 + +class sensor: + """ + A class to read the DHTXX temperature/humidity sensors. + """ + def __init__(self, sbc, chip, gpio, model=DHTAUTO, callback=None): + """ + Instantiate with the sbc, gpiochip, and the GPIO connected + to the DHT temperature and humidity sensor. + + Optionally the model of DHT may be specified. It may be one + of DHT11, DHTXX, or DHTAUTO. It defaults to DHTAUTO in which + case the model of DHT is automtically determined. + + Optionally a callback may be specified. If specified the + callback will be called whenever a new reading is available. + + The callback receives a tuple of timestamp, GPIO, status, + temperature, and humidity. + + The timestamp will be the number of seconds since the epoch + (start of 1970). + + The status will be one of: + 0 DHT_GOOD (a good reading) + 1 DHT_BAD_CHECKSUM (receieved data failed checksum check) + 2 DHT_BAD_DATA (data receieved had one or more invalid values) + 3 DHT_TIMEOUT (no response from sensor) + """ + self._sbc = sbc + self._chip = chip + self._gpio = gpio + self._model = model + + self._new_data = False + + self._bits = 0 + self._code = 0 + self._last_edge_tick = 0 + + self._timestamp = time.time() + self._status = DHT_TIMEOUT + self._temperature = 0.0 + self._humidity = 0.0 + + sbc.gpio_set_watchdog_micros(chip, gpio, 1000) # watchdog after 1 ms + self._cb = sbc.callback(chip, gpio, rgpio.RISING_EDGE, self._rising_edge) + + + def _datum(self): + return ((self._timestamp, self._gpio, self._status, + self._temperature, self._humidity)) + + def _validate_DHT11(self, b1, b2, b3, b4): + t = b2 + h = b4 + if (b1 == 0) and (b3 == 0) and (t <= 60) and (h >= 9) and (h <= 90): + valid = True + else: + valid = False + return (valid, t, h) + + def _validate_DHTXX(self, b1, b2, b3, b4): + if b2 & 128: + div = -10.0 + else: + div = 10.0 + t = float(((b2&127)<<8) + b1) / div + h = float((b4<<8) + b3) / 10.0 + if (h <= 110.0) and (t >= -50.0) and (t <= 135.0): + valid = True + else: + valid = False + return (valid, t, h) + + def _decode_dhtxx(self): + """ + +-------+-------+ + | DHT11 | DHTXX | + +-------+-------+ + Temp C| 0-50 |-40-125| + +-------+-------+ + RH% | 20-80 | 0-100 | + +-------+-------+ + + 0 1 2 3 4 + +------+------+------+------+------+ + DHT11 |check-| 0 | temp | 0 | RH% | + |sum | | | | | + +------+------+------+------+------+ + DHT21 |check-| temp | temp | RH% | RH% | + DHT22 |sum | LSB | MSB | LSB | MSB | + DHT33 | | | | | | + DHT44 | | | | | | + +------+------+------+------+------+ + """ + b0 = self._code & 0xff + b1 = (self._code >> 8) & 0xff + b2 = (self._code >> 16) & 0xff + b3 = (self._code >> 24) & 0xff + b4 = (self._code >> 32) & 0xff + + chksum = (b1 + b2 + b3 + b4) & 0xFF + + if chksum == b0: + if self._model == DHT11: + valid, t, h = self._validate_DHT11(b1, b2, b3, b4) + elif self._model == DHTXX: + valid, t, h = self._validate_DHTXX(b1, b2, b3, b4) + else: # AUTO + # Try DHTXX first. + valid, t, h = self._validate_DHTXX(b1, b2, b3, b4) + if not valid: + # try DHT11. + valid, t, h = self._validate_DHT11(b1, b2, b3, b4) + if valid: + self._timestamp = time.time() + self._temperature = t + self._humidity = h + self._status = DHT_GOOD + else: + self._status = DHT_BAD_DATA + else: + self._status = DHT_BAD_CHECKSUM + self._new_data = True + + def _rising_edge(self, chip, gpio, level, tick): + if level != rgpio.TIMEOUT: + edge_len = tick - self._last_edge_tick + self._last_edge_tick = tick + if edge_len > 2e8: # 0.2 seconds + self._bits = 0 + self._code = 0 + else: + self._code <<= 1 + if edge_len > 1e5: # 100 microseconds, so a high bit + self._code |= 1 + self._bits += 1 + else: # watchdog + if self._bits >= 30: + self._decode_dhtxx() + + def _trigger(self): + self._sbc.gpio_claim_output(self._chip, self._gpio, 0) + if self._model != DHTXX: + time.sleep(0.015) + else: + time.sleep(0.001) + self._bits = 0 + self._code = 0 + self._sbc.gpio_claim_alert(self._chip, self._gpio, rgpio.RISING_EDGE) + + def cancel(self): + """ + """ + if self._cb is not None: + self._cb.cancel() + self._cb = None + + def read(self): + """ + """ + self._new_data = False + self._status = DHT_TIMEOUT + self._trigger() + for i in range(20): # timeout after 1 seconds. + time.sleep(0.05) + if self._new_data: + break + if not self._new_data: + print("data timeout") + datum = self._datum() + return datum + +if __name__== "__main__": + import sys + import argparse + import rgpio + import DHT # import current module as a class + + ap = argparse.ArgumentParser() + + ap.add_argument("-c", "--gpiochip", help="gpiochip device number", + type=int, default=0) + + ap.add_argument("gpio", nargs="+", type=int) + + args = ap.parse_args() + + sbc = rgpio.sbc() + if not sbc.connected: + exit() + + chip = sbc.gpiochip_open(args.gpiochip) + + # Instantiate a class for each GPIO + + S = [] + for g in args.gpio: + s = DHT.sensor(sbc, chip, g) + S.append(s) # save class + + while True: + try: + for s in S: + d = s.read() + print("{:.3f} g={:2d} s={} t={:3.1f} rh={:3.1f}". + format(d[0], d[1], d[2], d[3], d[4])) + time.sleep(2) + except KeyboardInterrupt: + break + + for s in S: + s.cancel() + + sbc.stop() + diff --git a/EXAMPLES/py_rgpio/DS18B20.py b/EXAMPLES/py_rgpio/DS18B20.py new file mode 100755 index 0000000..64e045b --- /dev/null +++ b/EXAMPLES/py_rgpio/DS18B20.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python + +import time +import rgpio + +# DS18B20.py +# 2020-10-16 +# Public Domain + +""" +This uses the file interface to access the remote file system. + +In this case it is used to access the sysfs 1-wire bus interface +to read any connected DS18B20 temperature sensors. + +The remote permits file is used to grant access to +the remote file system. + +For this example the file must contain the following line which +grants read access to DS18B20 device files for the test1 user. + +[files] +test1=/sys/bus/w1/devices/28*/w1_slave r +""" +sbc = rgpio.sbc() + +if not sbc.connected: + exit() + +sbc.set_user("test1") + +while True: + + """ + Get list of connected sensors, handle error rather than the + default of raising exception if none found. + """ + + rgpio.exceptions = False + c, files = sbc.file_list("/sys/bus/w1/devices/28-00*/w1_slave") + rgpio.exceptions = True + + if c >= 0: + + for sensor in files[:-1].split("\n"): + + """ + Typical file name + + /sys/bus/w1/devices/28-000005d34cd2/w1_slave + """ + + devid = sensor.split("/")[5] # Fifth field is the device Id. + + h = sbc.file_open(sensor, rgpio.FILE_READ) + c, data = sbc.file_read(h, 1000) # 1000 is plenty to read full file. + sbc.file_close(h) + + """ + Typical file contents + + 73 01 4b 46 7f ff 0d 10 41 : crc=41 YES + 73 01 4b 46 7f ff 0d 10 41 t=23187 + """ + + if "YES" in data: + (discard, sep, reading) = data.partition(' t=') + t = float(reading) / 1000.0 + print("{} {:.1f}".format(devid, t)) + else: + print("999.9") + + time.sleep(3.0) + diff --git a/EXAMPLES/py_rgpio/bench.py b/EXAMPLES/py_rgpio/bench.py new file mode 100755 index 0000000..ecb1197 --- /dev/null +++ b/EXAMPLES/py_rgpio/bench.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python + +import time +import rgpio + +OUT=21 +LOOPS=2000 + +sbc = rgpio.sbc() +if not sbc.connected: + exit() + +h = sbc.gpiochip_open(0) + +sbc.gpio_claim_output(h, OUT) + +t0 = time.time() + +for i in range(LOOPS): + sbc.gpio_write(h, OUT, 0) + sbc.gpio_write(h, OUT, 1) + +t1 = time.time() + +sbc.gpiochip_close(h) + +print("{:.0f} toggles per second".format(LOOPS/(t1-t0))) + +sbc.stop() + diff --git a/EXAMPLES/py_rgpio/chipline.py b/EXAMPLES/py_rgpio/chipline.py new file mode 100755 index 0000000..015a6d6 --- /dev/null +++ b/EXAMPLES/py_rgpio/chipline.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python + +import rgpio + +sbc = rgpio.sbc() +if not sbc.connected: + exit() + +h = sbc.gpiochip_open(0) + +ci = sbc.gpio_get_chip_info(h) + +print("lines={} name={} label={}".format(ci[1], ci[2], ci[3])) + +for i in range(ci[1]): + li = sbc.gpio_get_line_info(h, i) + print("offset={} flags={} name={} user={}".format(li[1], li[2], li[3], li[4])) + +sbc.gpiochip_close(h) + diff --git a/EXAMPLES/py_rgpio/errors.py b/EXAMPLES/py_rgpio/errors.py new file mode 100755 index 0000000..ba9f96a --- /dev/null +++ b/EXAMPLES/py_rgpio/errors.py @@ -0,0 +1,465 @@ +#!/usr/bin/env python + +import time +import rgpio + +def check(func, exp, got): + if exp != got: + print("FAIL: {} (expected {}, got {})".format(func, exp, got)) + else: + print("PASS: {}".format(func)) + +def check_no_error(func, got): + if got < 0: + print("FAIL: {} (expected no error, got {})".format(func, got)) + else: + print("PASS: {} ({})".format(func, got)) + +def check_not_null(func, got): + if got == "": + print("FAIL: {} (expected name, got {})".format(func, got)) + else: + print("PASS: {} ({})".format(func, got)) + +pulses=[] +pulses.append(rgpio.pulse(0xff, 0xff, 1000)) +pulses.append(rgpio.pulse(0x00, 0x0f, 2000)) +pulses.append(rgpio.pulse(0xff, 0xf0, 3000)) +pulses.append(rgpio.pulse(0x00, 0xff, 4000)) +pulses.append(rgpio.pulse(0xff, 0x55, 5000)) +pulses.append(rgpio.pulse(0x00, 0xAA, 6000)) + +sbc = rgpio.sbc() + +if not sbc.connected: + exit() + +rgpio.exceptions = False + +status = sbc.set_user("admin", ".lg_secret") + +status = sbc.set_internal(0, 2) + +status, value = sbc.get_internal(0) +check("get_internal", 3, value) + +status = sbc.set_internal(0, 3) +check("set_internal", rgpio.OKAY, status) + +status = sbc.set_user("test1", ".lg_secret") + +status = sbc.file_close(9999) +check("file_close", rgpio.BAD_HANDLE, status) + +status, dummy = sbc.file_list("TEST/pattern/*") +check("file_list 1", rgpio.NO_FILE_MATCH, status) + +status, dummy = sbc.file_list("..") +check("file_list 2", rgpio.NO_FILE_ACCESS, status) + +status = sbc.file_open("TEST/file", 9999) +check("file_open 1", rgpio.BAD_FILE_MODE, status) + +status = sbc.file_open("TEST/file", 1) +check("file_open 2", rgpio.FILE_OPEN_FAILED, status) + +status = sbc.file_open("missing_file", 1) +check("file_open 3", rgpio.FILE_OPEN_FAILED, status) + +status, dummy = sbc.file_read(9999, 0) +check("file_read 1", rgpio.BAD_FILE_PARAM, status) + +status, dummy = sbc.file_read(9999, 8888) +check("file_read 2", rgpio.BAD_HANDLE, status) + +status = sbc.file_seek(9999, 8888, 7777) +check("file_seek 1", rgpio.BAD_FILE_SEEK, status) + +status = sbc.file_seek(9999, 8888, 1) +check("file_seek 2", rgpio.BAD_HANDLE, status) + +status = sbc.file_write(9999, []) +check("file_write 1", rgpio.BAD_FILE_PARAM, status) + +status = sbc.file_write(9999, "Hello") +check("file_write 2", rgpio.BAD_HANDLE, status) + +status = sbc.i2c_close(9999) +check("i2c_close", rgpio.BAD_HANDLE, status) + +status = sbc.i2c_open(1, 8888, 7777) +check("i2c_open 1", rgpio.BAD_I2C_ADDR, status) + +status = sbc.i2c_open(1, 0x40, 7777) +check("i2c_open 2", rgpio.BAD_I2C_FLAGS, status) + +status = sbc.i2c_open(999, 0x40, 0) +check("i2c_open 3", rgpio.BAD_I2C_BUS, status) + +status = sbc.i2c_process_call(9999, 8888, 7777) +check("i2c_process_call 1", rgpio.BAD_I2C_PARAM, status) + +status = sbc.i2c_process_call(9999, 5, 77777) +check("i2c_process_call 2", rgpio.BAD_I2C_PARAM, status) + +status = sbc.i2c_process_call(9999, 0xff, 0xffff) +check("i2c_process_call 3", rgpio.BAD_HANDLE, status) + +status, dummy = sbc.i2c_block_process_call(9999, 256, [77, 66, 55, 44, 33, 22, 11]) +check("i2c_block_process_call 1", rgpio.BAD_I2C_PARAM, status) + +status, dummy = sbc.i2c_block_process_call(9999, 23, []) +check("i2c_block_process_call 2", rgpio.BAD_I2C_PARAM, status) + +status, dummy = sbc.i2c_block_process_call(9999, 23, [77, 66, 55, 44, 33, 22, 11]) +check("i2c_block_process_call 3", rgpio.BAD_HANDLE, status) + +status = sbc.i2c_read_byte_data(9999, 8888) +check("i2c_read_byte_data 1", rgpio.BAD_I2C_PARAM, status) + +status = sbc.i2c_read_byte_data(9999, 250) +check("i2c_read_byte_data 2", rgpio.BAD_HANDLE, status) + +status, dummy = sbc.i2c_read_device(9999, 0) +check("i2c_read_device 1", rgpio.BAD_I2C_PARAM, status) + +status, dummy = sbc.i2c_read_device(9999, 250) +check("i2c_read_device 2", rgpio.BAD_HANDLE, status) + +status, dummy = sbc.i2c_read_i2c_block_data(9999, 8888, 20) +check("i2c_read_i2c_block_data 1", rgpio.BAD_I2C_PARAM, status) + +status, dummy = sbc.i2c_read_i2c_block_data(9999, 250, 33) +check("i2c_read_i2c_block_data 2", rgpio.BAD_I2C_PARAM, status) + +status, dummy = sbc.i2c_read_i2c_block_data(9999, 250, 30) +check("i2c_read_i2c_block_data 3", rgpio.BAD_HANDLE, status) + +status, dummy = sbc.i2c_read_block_data(9999, 8888) +check("i2c_read_block_data 1", rgpio.BAD_I2C_PARAM, status) + +status, dummy = sbc.i2c_read_block_data(9999, 25) +check("i2c_read_block_data 2", rgpio.BAD_HANDLE, status) + +status = sbc.i2c_read_byte(9999) +check("i2c_read_byte 1", rgpio.BAD_HANDLE, status) + +status = sbc.i2c_read_word_data(9999, 8888) +check("i2c_read_word_data 1", rgpio.BAD_I2C_PARAM, status) + +status = sbc.i2c_read_word_data(9999, 88) +check("i2c_read_word_data 2", rgpio.BAD_HANDLE, status) + +status = sbc.i2c_write_byte_data(9999, 8888, 7777) +check("i2c_write_byte_data 1", rgpio.BAD_I2C_PARAM, status) + +status = sbc.i2c_write_byte_data(9999, 8, 777) +check("i2c_write_byte_data 2", rgpio.BAD_I2C_PARAM, status) + +status = sbc.i2c_write_byte_data(9999, 8, 77) +check("i2c_write_byte_data 3", rgpio.BAD_HANDLE, status) + +status = sbc.i2c_write_device(9999, []) +check("i2c_write_device 1", rgpio.BAD_I2C_PARAM, status) + +status = sbc.i2c_write_device(9999, [88, 77, 66, 55, 44, 33, 22, 11]) +check("i2c_write_device 2", rgpio.BAD_HANDLE, status) + +status = sbc.i2c_write_i2c_block_data(9999, 8888, [77, 66, 55, 44, 33, 22, 11]) +check("i2c_write_i2c_block_data 1", rgpio.BAD_I2C_PARAM, status) + +status = sbc.i2c_write_i2c_block_data(9999, 88, []) +check("i2c_write_i2c_block_data 2", rgpio.BAD_I2C_PARAM, status) + +status = sbc.i2c_write_i2c_block_data(9999, 88, [77, 66, 55, 44, 33, 22, 11]) +check("i2c_write_i2c_block_data 3", rgpio.BAD_HANDLE, status) + +status = sbc.i2c_write_block_data(9999, 256, [77, 66, 55, 44, 33, 22, 11]) +check("i2c_write_block_data 1", rgpio.BAD_I2C_PARAM, status) + +status = sbc.i2c_write_block_data(9999, 55, []) +check("i2c_write_block_data 2", rgpio.BAD_I2C_PARAM, status) + +status = sbc.i2c_write_block_data(9999, 55, [77, 66, 55, 44, 33, 22, 11]) +check("i2c_write_block_data 3", rgpio.BAD_HANDLE, status) + +status = sbc.i2c_write_quick(9999, 2) +check("i2c_write_quick 1", rgpio.BAD_I2C_PARAM, status) + +status = sbc.i2c_write_quick(9999, 1) +check("i2c_write_quick 2", rgpio.BAD_HANDLE, status) + +status = sbc.i2c_write_byte(9999, 8888) +check("i2c_write_byte 1", rgpio.BAD_I2C_PARAM, status) + +status = sbc.i2c_write_byte(9999, 255) +check("i2c_write_byte 2", rgpio.BAD_HANDLE, status) + +status = sbc.i2c_write_word_data(9999, 8888, 7777) +check("i2c_write_word_data 1", rgpio.BAD_I2C_PARAM, status) + +status = sbc.i2c_write_word_data(9999, 88, 77777) +check("i2c_write_word_data 2", rgpio.BAD_I2C_PARAM, status) + +status = sbc.i2c_write_word_data(9999, 88, 7777) +check("i2c_write_word_data 3", rgpio.BAD_HANDLE, status) + +status, dummy = sbc.i2c_zip(9999, []) +check("i2c_zip 1", rgpio.BAD_POINTER, status) + +status, dummy = sbc.i2c_zip(9999, [88, 77, 66, 55, 44, 33, 22, 11]) +check("i2c_zip 2", rgpio.BAD_HANDLE, status) + +server = sbc.get_sbc_name() +check_not_null("get_sbc_name", server) + +status = rgpio.get_module_version() +check_not_null("get_module_version", status) + +sbc.set_user("test2", ".lg_secret") + +h = sbc.notify_open() +check_no_error("notify_open 1", h) + +status = sbc.notify_resume(h) +check("notify_resume 1", rgpio.OKAY, status) + +status = sbc.notify_resume(9999) +check("notify_resume 2", rgpio.BAD_HANDLE, status) + +status = sbc.notify_pause(h) +check("notify_pause 1", rgpio.OKAY, status) + +status = sbc.notify_pause(9999) +check("notify_pause 2", rgpio.BAD_HANDLE, status) + +status = sbc.notify_close(h) +check("notify_close 1", rgpio.OKAY, status) + +status = sbc.notify_close(9999) +check("notify_close 2", rgpio.BAD_HANDLE, status) + +sbc.set_user("test3", ".lg_secret") + +h = sbc.script_store("tag 0 dcr p1 mils 100 jmp 0") +check_no_error("script store 1", h) + +time.sleep(0.2) + +status, dummy = sbc.script_status(h) +check("script_status 1", rgpio.SCRIPT_READY, status) + +status, dummy = sbc.script_status(9999) +check("script_status 2", rgpio.BAD_HANDLE, status) + +status = sbc.script_update(h, [5555, 4444, 3333, 2222, 1111]) +check("script_update 1", rgpio.OKAY, status) + +status = sbc.script_update(9999, [5555, 4444, 3333, 2222, 1111]) +check("script_update 2", rgpio.BAD_HANDLE, status) + +status = sbc.script_run(h, [8888, 7777, 6666, 5555]) +check("script_run 1", rgpio.OKAY, status) + +status = sbc.script_run(8888, [8888, 7777, 6666, 5555]) +check("script_run 2", rgpio.BAD_HANDLE, status) + +status = sbc.script_stop(h) +check("script_stop 1", rgpio.OKAY, status) + +status = sbc.script_stop(9999) +check("script_stop 2", rgpio.BAD_HANDLE, status) + +status = sbc.script_delete(h) +check("script_delete 1", rgpio.OKAY, status) + +status = sbc.script_delete(9999) +check("script_delete 2", rgpio.BAD_HANDLE, status) + +status = sbc.set_user("test3", ".lg_secret") + +status = sbc.serial_open("raw", 9600, 0) +check("serial_open 1", rgpio.SERIAL_OPEN_FAILED, status) + +status = sbc.set_user("test1", ".lg_secret") + +status = sbc.serial_open("ttyS0", 8888, 7777) +check("serial_open 2", rgpio.BAD_SERIAL_SPEED, status) + +status = sbc.serial_open("ttyS0", 9600, 7777) +check("serial_open 3", rgpio.BAD_SERIAL_FLAGS, status) + +status = sbc.serial_open("ttyUnlikelyFileName", 9600, 0) +check("serial_open 4", rgpio.SERIAL_OPEN_FAILED, status) + +status = sbc.serial_close(9999) +check("serial_close 1", rgpio.BAD_HANDLE, status) + +status = sbc.serial_data_available(9999) +check("serial_data_available 1", rgpio.BAD_HANDLE, status) + +status, dummy = sbc.serial_read(9999, 0) +check("serial_read 1", rgpio.BAD_SERIAL_PARAM, status) + +status, dummy = sbc.serial_read(9999, 8888) +check("serial_read 2", rgpio.BAD_HANDLE, status) + +status = sbc.serial_read_byte(9999) +check("serial_read_byte 1", rgpio.BAD_HANDLE, status) + +status = sbc.serial_write(9999, []) +check("serial_write 1", rgpio.BAD_SERIAL_PARAM, status) + +status = sbc.serial_write(9999, [88, 77, 66, 55, 44, 33, 22, 11]) +check("serial_write 2", rgpio.BAD_HANDLE, status) + +status = sbc.serial_write_byte(9999, 256) +check("serial_write_byte 2", rgpio.BAD_SERIAL_PARAM, status) + +status = sbc.serial_write_byte(9999, 88) +check("serial_write_byte 2", rgpio.BAD_HANDLE, status) + +status = sbc.set_user("test3", ".lg_secret") + +status = sbc.shell("echo", "me") +check("shell 1", 32512, status) + +status = sbc.set_user("test1", ".lg_secret") + +status = sbc.spi_close(9999) +check("spi_close 1", rgpio.BAD_HANDLE, status) + +status = sbc.spi_open(2, 1, 7777, 6666) +check("spi_open 1", rgpio.SPI_OPEN_FAILED, status) + +status, dummy = sbc.spi_read(9999, 0) +check("spi_read 1", rgpio.BAD_SPI_COUNT, status) + +status, dummy = sbc.spi_read(9999, 8888) +check("spi_read 2", rgpio.BAD_HANDLE, status) + +status = sbc.spi_write(9999, []) +check("spi_write 1", rgpio.BAD_SPI_COUNT, status) + +status = sbc.spi_write(9999, [88, 77, 66, 55, 44, 33, 22, 11]) +check("spi_write 2", rgpio.BAD_HANDLE, status) + +status, dummy = sbc.spi_xfer(9999, []) +check("spi_xfer 1", rgpio.BAD_SPI_COUNT, status) + +status, dummy = sbc.spi_xfer(9999, [88, 77, 66, 55, 44, 33, 22, 11]) +check("spi_xfer 2", rgpio.BAD_HANDLE, status) + +status = sbc.gpiochip_close(9999) +check("gpiochip_close 1", rgpio.BAD_HANDLE, status) + +status, dummy = sbc.group_read(9999, 8888) +check("group_read 1", rgpio.BAD_HANDLE, status) + +status = sbc.group_write(9999, 8888, 7777) +check("group_write 1", rgpio.BAD_HANDLE, status) + +status = sbc.gpiochip_open(9999) +check("gpiochip_open 1", rgpio.CANNOT_OPEN_CHIP, status) + +status, lines, name, label = sbc.gpio_get_chip_info(9999) +check("gpio_get_chip_info 1", rgpio.BAD_HANDLE, status) + +status, offset, flags, name, user = sbc.gpio_get_line_info(9999, 8888) +check("gpio_get_line_info 1", rgpio.BAD_HANDLE, status) + +status = sbc.gpio_get_mode(9999, 8888) +check("gpio_get_mode 1", rgpio.BAD_HANDLE, status) + +status = sbc.gpio_read(9999, 8888) +check("gpio_read 1", rgpio.BAD_HANDLE, status) + +status = sbc.gpio_free(9999, 8888) +check("gpio_free 1", rgpio.BAD_HANDLE, status) + +status = sbc.group_free(9999, 8888) +check("group_free 1", rgpio.BAD_HANDLE, status) + +status = sbc.tx_pulse(9999, 8888,7777, 6666, 5555, 4444) +check("tx_pulse 1", rgpio.BAD_HANDLE, status) + +status = sbc.tx_pwm(9999, 8888, 7777, 6666, 5555, 4444) +check("tx_pwm 1", rgpio.BAD_PWM_DUTY, status) + +status = sbc.tx_pwm(9999, 8888, 77777, 50, 5555, 4444) +check("tx_pwm 2", rgpio.BAD_PWM_FREQ, status) + +status = sbc.tx_pwm(9999, 8888, 5000, 50, 5555, 4444) +check("tx_pwm 3", rgpio.BAD_HANDLE, status) + +status = sbc.tx_servo(9999, 8888, 7777, 6666, 5555, 4444) +check("tx_servo 1", rgpio.BAD_SERVO_FREQ, status) + +status = sbc.tx_servo(9999, 8888, 7777, 50, 5555, 4444) +check("tx_servo 2", rgpio.BAD_SERVO_WIDTH, status) + +status = sbc.tx_servo(9999, 8888, 1500, 50, 5555, 4444) +check("tx_servo 2", rgpio.BAD_HANDLE, status) + +status = sbc.tx_wave(9999, 8888, pulses) +check("tx_wave 1", rgpio.BAD_HANDLE, status) + +status = sbc.tx_busy(9999, 8888, 7777) +check("tx_busy 1", rgpio.BAD_TX_TYPE, status) + +status = sbc.tx_busy(9999, 8888, 0) +check("tx_busy 2", rgpio.BAD_HANDLE, status) + +status = sbc.tx_room(9999, 8888, 7777) +check("tx_room 1", rgpio.BAD_TX_TYPE, status) + +status = sbc.tx_room(9999, 8888, 0) +check("tx_room 2", rgpio.BAD_HANDLE, status) + +status = sbc.gpio_set_debounce_micros(9999, 8888, 6000000) +check("gpio_set_debounce_micros 1", rgpio.BAD_DEBOUNCE_MICS, status) + +status = sbc.gpio_set_debounce_micros(9999, 8888, 600) +check("gpio_set_debounce_micros 2", rgpio.BAD_HANDLE, status) + +status = sbc.gpio_set_watchdog_micros(9999, 8888, 400000000) +check("gpio_set_watchdog_micros 1", rgpio.BAD_WATCHDOG_MICS, status) + +status = sbc.gpio_set_watchdog_micros(9999, 8888, 4000) +check("gpio_set_watchdog_micros 2", rgpio.BAD_HANDLE, status) + +status = sbc.gpio_claim_alert(9999, 8888, 7777, 6666, 5555) +check("gpio_claim_alert 1", rgpio.BAD_HANDLE, status) + +status = sbc.gpio_claim_input(9999, 8888) +check("gpio_claim_input 1", rgpio.BAD_HANDLE, status) + +status = sbc.group_claim_input(9999, [8888, 7777, 6666]) +check("group_claim_input 1", rgpio.BAD_HANDLE, status) + +status = sbc.gpio_claim_output(9999, 8888, 7777) +check("gpio_claim_output 1", rgpio.BAD_HANDLE, status) + +status = sbc.group_claim_output(9999, [8888, 7777, 6666]) +check("group_claim_output 1", rgpio.BAD_HANDLE, status) + +status = sbc.gpio_write(9999, 8888, 7777) +check("gpio_write 1", rgpio.BAD_HANDLE, status) + +status = sbc.set_share_id(9999, 8888) +check("set_share_id 1", rgpio.BAD_HANDLE, status) + +status = sbc.use_share_id(9999) +check("use_share_id 1", rgpio.OKAY, status) + +err = rgpio.error_text(0) +check("error_text 1", "No error", err) + +err = rgpio.error_text(-1) +check("error_text 2", "initialisation failed", err) + +err = rgpio.error_text(1) +check("error_text 3", "unknown error", err) + +sbc.stop() + diff --git a/EXAMPLES/py_rgpio/files.py b/EXAMPLES/py_rgpio/files.py new file mode 100755 index 0000000..09c0f24 --- /dev/null +++ b/EXAMPLES/py_rgpio/files.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python + +import rgpio + +def check(func, exp, got): + if exp != got: + print("FAIL: {} (expected {}, got {})".format(func, exp, got)) + else: + print("PASS: {}".format(func)) + +def check_no_error(func, got): + if got < 0: + print("FAIL: {} (expected no error, got {})".format(func, got)) + else: + print("PASS: {} ({})".format(func, got)) + +def check_error(func, got): + if got < 0: + print("PASS: {} ({})".format(func, got)) + else: + print("FAIL: {} (expected error, got {})".format(func, got)) + +def check_not_null(func, got): + if got == "": + print("FAIL: {} (expected name, got {})".format(func, got)) + else: + print("PASS: {} ({})".format(func, got)) + +sbc = rgpio.sbc() +if not sbc.connected: + exit() + +sbc.set_user("test1") + +# open file for write, create if it doesn't exist + +h = sbc.file_open( + "test.file", rgpio.FILE_WRITE+rgpio.FILE_CREATE+rgpio.FILE_TRUNC) +check_no_error("file_open", h) + +# write some text + +s = sbc.file_write(h, "Now is the winter of our discontent\n") +check_no_error("file_write", s) + +s = sbc.file_write(h, "Made glorious summer by this son of York\n") +check_no_error("file_write", s) + +s = sbc.file_close(h) +check_no_error("file_close", s) + +# open for read + +h = sbc.file_open("test.file", rgpio.FILE_READ) +check_no_error("file_open", h) + +(s, data) = sbc.file_read(h, 100) # read up to 100 characters from file +check("file_read", 77, s) + +s = sbc.file_seek(h, 25, rgpio.FROM_START) +check_no_error("file_seek", s) + +(s, data) = sbc.file_read(h, 100) # read up to 100 characters from file +check("file_read", 52, s) + +s = sbc.file_seek(h, -25, rgpio.FROM_END) +check_no_error("file_seek", s) + +(s, data) = sbc.file_read(h, 100) # read up to 100 charactrs from file +check("file_read", 25, s) + +s = sbc.file_seek(h, -50, rgpio.FROM_END) +check_no_error("file_seek", s) + +(s, data) = sbc.file_read(h, 5) # read up to 5 charactrs from file +check("file_read", 5, s) + +sbc.file_seek(h, -20, rgpio.FROM_CURRENT) +check_no_error("file_seek", s) + +(s, data) = sbc.file_read(h, 100) # read up to 100 charactrs from file +check("file_read", 65, s) + +s = sbc.file_close(h) +check_no_error("file_close", s) + +(s, data) = sbc.file_list("file*") +check("file_list", 32, s) + +rgpio.exceptions = False + +for pat in "/", "/tmp", "*", "TEST": + (s, data) = sbc.file_list(pat) + check("file_list (" + pat + ")", rgpio.NO_FILE_ACCESS, s) + +for pat in "/tmp/unlikely_file_name", "file34": + (s, data) = sbc.file_list(pat) + check("file_list (" + pat + ")", rgpio.NO_FILE_MATCH, s) + +for pat in "/tmp/*", "file.*": + (s, data) = sbc.file_list(pat) + check_no_error("file_list (" + pat + ")", s) + +# create a file + +h = sbc.file_open( + "/tmp/unlikely_file", rgpio.FILE_WRITE+rgpio.FILE_CREATE+rgpio.FILE_TRUNC) +check_no_error("file_open", h) + +s = sbc.file_close(h) +check_no_error("file_close", s) + +expected = [ + rgpio.NO_FILE_ACCESS, 1, 1, 1, + rgpio.NO_FILE_ACCESS, rgpio.NO_FILE_ACCESS, 1, 1, + rgpio.NO_FILE_ACCESS, rgpio.NO_FILE_ACCESS, 1, 1, + rgpio.NO_FILE_ACCESS, rgpio.NO_FILE_ACCESS, 1, 1, + rgpio.NO_FILE_ACCESS, rgpio.NO_FILE_ACCESS, 1, 1, + rgpio.NO_FILE_ACCESS, rgpio.NO_FILE_ACCESS, 1, 1, + rgpio.NO_FILE_ACCESS, rgpio.NO_FILE_ACCESS, 1, 1, + rgpio.NO_FILE_ACCESS, rgpio.NO_FILE_ACCESS, 1, 1] + + +fails = 0 + +for mode in range(0, 32): + h = sbc.file_open("/tmp/unlikely_file", mode) + if h < 0: + if h != expected[mode]: + fails += 1 + print("A: for {} expected {}, got {}".format(mode, expected[mode], h)) + else: + if expected[mode] < 0: + fails += 1 + print("A: for {} expected ok, got {}".format(mode, h)) + sbc.file_close(h) +check("A: file_open", 0, fails) + +expected = [ + rgpio.NO_FILE_ACCESS, rgpio.NO_FILE_ACCESS, + rgpio.NO_FILE_ACCESS, rgpio.NO_FILE_ACCESS, + rgpio.NO_FILE_ACCESS, rgpio.NO_FILE_ACCESS, + rgpio.NO_FILE_ACCESS, rgpio.NO_FILE_ACCESS, + rgpio.NO_FILE_ACCESS, rgpio.NO_FILE_ACCESS, + rgpio.NO_FILE_ACCESS, rgpio.NO_FILE_ACCESS, + rgpio.NO_FILE_ACCESS, rgpio.NO_FILE_ACCESS, + rgpio.NO_FILE_ACCESS, rgpio.NO_FILE_ACCESS, + rgpio.NO_FILE_ACCESS, rgpio.NO_FILE_ACCESS, + rgpio.NO_FILE_ACCESS, rgpio.NO_FILE_ACCESS, + rgpio.NO_FILE_ACCESS, rgpio.NO_FILE_ACCESS, + rgpio.NO_FILE_ACCESS, rgpio.NO_FILE_ACCESS, + rgpio.NO_FILE_ACCESS, rgpio.NO_FILE_ACCESS, + rgpio.NO_FILE_ACCESS, rgpio.NO_FILE_ACCESS, + rgpio.NO_FILE_ACCESS, rgpio.NO_FILE_ACCESS, + rgpio.NO_FILE_ACCESS, rgpio.NO_FILE_ACCESS] + +fails = 0 + +for mode in range(0, 32): + h = sbc.file_open("unlikely_file", mode) + if h < 0: + if h != expected[mode]: + fails += 1 + print("B: for {} expected {}, got {}".format(mode, expected[mode], h)) + else: + if expected[mode] < 0: + fails += 1 + print("B: for {} expected ok, got {}".format(mode, h)) + sbc.file_close(h) +check("B: file_open", 0, fails) + +fails = 0 + +for pat in "./zzz", "././yyy", "\.\./xxx", "../yyy", "./yyy": + h = sbc.file_open(pat, rgpio.FILE_WRITE+rgpio.FILE_CREATE+rgpio.FILE_TRUNC) + if h != rgpio.NO_FILE_ACCESS: + fails += 1 + print("FAIL: for {} expected no access".format(root+pat)) + sbc.file_close(h) +check("file_open", 0, fails) + +rgpio.exceptions = True + +sbc.stop() + diff --git a/EXAMPLES/py_rgpio/testbed.py b/EXAMPLES/py_rgpio/testbed.py new file mode 100755 index 0000000..ff93bdb --- /dev/null +++ b/EXAMPLES/py_rgpio/testbed.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python + +# testbed.py +# 2020-09-20 +# Public Domain + +import time +import rgpio + +MCP23017_1=0x20 +MCP23017_2=0x21 +NANO=0x2c + +DAC_CS=8 # spidev 0.0 +ADC_CS=25 # spidev 0.1 +NANO_CS=24 # spidev 0.1 + +sbc = rgpio.sbc() +if not sbc.connected: + exit() + +chip = sbc.gpiochip_open(0) +sbc.gpio_claim_output(chip, 0, ADC_CS, 1) +sbc.gpio_claim_output(chip, 0, NANO_CS, 1) + +dac = sbc.spi_open(0, 0, 500000) +others = sbc.spi_open(0, 1, 50000) + +nano_i2c = sbc.i2c_open(1, NANO) + +nano_serial = sbc.serial_open("serial0", 115200) + +inc = True + +potpos = 0 + +while True: + + while True: + + sbc.spi_write(dac, [0, potpos]) + + for i in range(8): + sbc.gpio_write(chip, ADC_CS, 0) + (b, d) = sbc.spi_xfer(others, [1, 0x80+(i<<4), 0]) + sbc.gpio_write(chip, ADC_CS, 1) + + if b == 3: + c1 = d[1] & 0x03 + c2 = d[2] + ch0 = (c1<<8)+c2 + else: + ch0 = -1 + + print("ADC{}={:4d} pot={}".format(i, ch0, potpos)) + + potpos += 1 + if potpos > 129: + potpos = 0 + + sbc.i2c_write_device(nano_i2c, "Hello world!") + + time.sleep(0.1) + + (b, d) = sbc.serial_read(nano_serial) + + print(d) + + sbc.serial_write(nano_serial, "0random chars\n9") + + time.sleep(0.2) + + (b, d) = sbc.serial_read(nano_serial) + + print(d) + + sbc.gpio_write(chip, NANO_CS, 0) + (b, d) = sbc.spi_xfer(others, "A message to SPI\n") + sbc.gpio_write(chip, NANO_CS, 1) + + time.sleep(0.2) + + (b, d) = sbc.serial_read(nano_serial) + + print(d) + diff --git a/EXAMPLES/py_rgpio/tx_pulse.py b/EXAMPLES/py_rgpio/tx_pulse.py new file mode 100755 index 0000000..6cf2374 --- /dev/null +++ b/EXAMPLES/py_rgpio/tx_pulse.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python + +import time +import rgpio + +OUT=21 +LOOPS=120 + +sbc = rgpio.sbc() +if not sbc.connected: + exit() + +h = sbc.gpiochip_open(0) + +sbc.gpio_claim_output(h, OUT) + +sbc.tx_pulse(h, OUT, 20000, 30000) # 20 Hz 40 % duty cycle + +time.sleep(2) + +sbc.tx_pulse(h, OUT, 20000, 5000, pulse_cycles=LOOPS) # 40 Hz 80 % + +start = time.time() + +while sbc.tx_busy(h, OUT, rgpio.TX_PWM): + time.sleep(0.01) + +end = time.time() + +print("{} cycles at 40 Hz took {:.1f} seconds".format(LOOPS, end-start)) + +sbc.gpiochip_close(h) + diff --git a/EXAMPLES/py_rgpio/tx_wave.py b/EXAMPLES/py_rgpio/tx_wave.py new file mode 100755 index 0000000..7ff9fe3 --- /dev/null +++ b/EXAMPLES/py_rgpio/tx_wave.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python + +import time +import rgpio + +OUT=[20, 21, 22, 23, 24, 25] + +PULSES=500 + +sbc = rgpio.sbc() +if not sbc.connected: + exit() + +pulses = [] +total = 0 +delay = 1000 + +for i in range(PULSES): + pulses.append(rgpio.pulse(i, rgpio.GROUP_ALL, delay)) + total += delay + delay += 100 + +h = sbc.gpiochip_open(0) + +sbc.group_claim_output(h, OUT) + +sbc.tx_wave(h, OUT[0], pulses) + +start = time.time() + +while sbc.tx_busy(h, OUT[0], rgpio.TX_WAVE): + time.sleep(0.01) + +end = time.time() + +print("{} pulses took {:.1f} seconds (exp={:.1f})". + format(PULSES, end-start, total/1e6)) + +sbc.gpiochip_close(h) + +sbc.stop() + diff --git a/EXAMPLES/rgpio/DS18B20.c b/EXAMPLES/rgpio/DS18B20.c new file mode 100644 index 0000000..0cf5ddb --- /dev/null +++ b/EXAMPLES/rgpio/DS18B20.c @@ -0,0 +1,115 @@ +#include +#include +#include + +#include +#include + +/* +# DS18B20.py +# 2020-09-09 +# Public Domain + +gcc -Wall -o DS18B20 DS18B20.c -lrgpio + +./DS18B20 + +This uses the file interface to access the remote file system. + +In this case it is used to access the sysfs 1-wire bus interface +to read any connected DS18B20 temperature sensors. + +The remote permits file is used to grant access to +the remote file system. + +For this example the file must contain the following line which +grants read access to DS18B20 device files for the test1 user. + +[files] +test1=/sys/bus/w1/devices/28*\/w1_slave r + +*/ + +int main(int argc, char *argv[]) +{ + int sbc; + int bytes; + int h; + char sensor[1024]; + char data[256]; + char *nameSP; + char *nameEP; + char *dataP; + float t; + + sbc = rgpiod_start(0, 0); + + if (sbc < 0) + { + fprintf(stderr, "lg initialisation failed (sbc, %d).\n", sbc); + return 1; + } + + printf("Connected to lg daemon (%d).\n", sbc); + + lgu_set_user(sbc, "test1", ".lg_secret"); + + while (1) + { + /* Get list of connected sensors. */ + bytes = file_list( + sbc, "/sys/bus/w1/devices/28-00*/w1_slave", sensor, sizeof(sensor)); + + if (bytes >= 0) + { + nameSP = sensor; + while ((nameEP=strchr(nameSP, '\n')) != NULL) + { + t = 999.99; + + *nameEP = 0; + + /* + + Typical file name + + /sys/bus/w1/devices/28-000005d34cd2/w1_slave + + */ + + h = file_open(sbc, nameSP, LG_FILE_READ); + + if (h >= 0) + { + file_read(sbc, h, data, sizeof(data)); + file_close(sbc, h); + + /* + Typical file contents + + 73 01 4b 46 7f ff 0d 10 41 : crc=41 YES + 73 01 4b 46 7f ff 0d 10 41 t=23187 + */ + + if (strstr(data, "YES") != NULL) + { + dataP = strstr(data, "t="); + if (dataP != NULL) + { + t = atoi(dataP+2) / 1000.0; + } + } + } + + nameSP += 20; /* point to device id */ + *(nameSP+15) = 0; /* null terminate device id */ + printf("%s %.1f\n", nameSP, t); + nameSP = nameEP+1; /* move to next line */ + } + } + else printf("no sensors found\n"); + + lgu_sleep(3.0); + } +} + diff --git a/EXAMPLES/rgpio/bench.c b/EXAMPLES/rgpio/bench.c new file mode 100644 index 0000000..4f464f8 --- /dev/null +++ b/EXAMPLES/rgpio/bench.c @@ -0,0 +1,52 @@ +#include +#include + +#include +#include + +#define LFLAGS 0 + +#define OUT 21 +#define LOOPS 5000 + +int main(int argc, char *argv[]) +{ + int sbc; + int h; + int i; + double t0, t1; + + sbc = rgpiod_start(NULL, NULL); + + if (sbc < 0) + { + printf("connection failed\n"); + exit(-1); + } + + h = gpiochip_open(sbc, 0); + + if (h >= 0) + { + if (gpio_claim_output(sbc, h, LFLAGS, OUT, 0) == LG_OKAY) + { + + t0 = lgu_time(); + + for (i=0; i +#include + +#include +#include + +int main(int argc, char *argv[]) +{ + int sbc; + int h; + int i; + lgChipInfo_t cinf; + lgLineInfo_t linf; + + sbc = rgpiod_start(NULL, NULL); + + if (sbc < 0) + { + printf("connection failed\n"); + exit(-1); + } + + h = gpiochip_open(sbc, 0); + + if (h >= 0) + { + if (gpio_get_chip_info(sbc, h, &cinf) == LG_OKAY) + { + printf("%d \"%s\" \"%s\"\n", cinf.lines, cinf.name, cinf.label); + + for (i=0; i +#include +#include +#include +#include + +#include + +struct DHTXXD_s; + +typedef struct DHTXXD_s DHTXXD_t; + +#define DHTAUTO 0 +#define DHT11 1 +#define DHTXX 2 + +#define DHT_GOOD 0 +#define DHT_BAD_CHECKSUM 1 +#define DHT_BAD_DATA 2 +#define DHT_TIMEOUT 3 + +typedef struct +{ + int sbc; + int chip; + int gpio; + int status; + float temperature; + float humidity; + double timestamp; +} DHTXXD_data_t; + +typedef void (*DHTXXD_CB_t)(DHTXXD_data_t); + +/* +DHTXXD starts a DHTXX sensor on sbc with GPIO gpio. + +The model may be auto detected (DHTAUTO), or specified as a +DHT11 (DHT11), or DHT21/22/33/44 (DHTXX). + +If cb_func is not null it will be called at each new reading +whether the received data is valid or not. The callback +receives a DHTXXD_data_t object. + +If cb_func is null then the DHTXXD_ready function should be +called to check for new data which may then be retrieved by +a call to DHTXXD_data. + +A single reading may be triggered with DHTXXD_manual_read. + +A reading may be triggered at regular intervals using +DHTXXD_auto_read. If the auto trigger interval is 30 +seconds or greater two readings will be taken and only +the second will be returned. I would not read the +DHT22 more than once every 3 seconds. The DHT11 can +safely be read once a second. I don't know about the +other models. + +At program end the DHTXX sensor should be cancelled using +DHTXXD_cancel. This releases system resources. +*/ + +DHTXXD_t *DHTXXD (int sbc, + int chip, + int gpio, + int model, + DHTXXD_CB_t cb_func); + +void DHTXXD_cancel (DHTXXD_t *self); + +int DHTXXD_ready (DHTXXD_t *self); + +DHTXXD_data_t DHTXXD_data (DHTXXD_t *self); + +void DHTXXD_manual_read (DHTXXD_t *self); + +void DHTXXD_auto_read (DHTXXD_t *self, float seconds); + +/* + +Code to read the DHTXX temperature/humidity sensors. + +*/ + +/* PRIVATE ---------------------------------------------------------------- */ + +struct DHTXXD_s +{ + int sbc; + int chip; + int gpio; + int model; + int seconds; + DHTXXD_CB_t cb; + int _cb_id; + pthread_t *_pth; + union + { + uint8_t _byte[8]; + uint64_t _code; + }; + int _bits; + int _ready; + int _new_reading; + DHTXXD_data_t _data; + uint64_t _last_edge_tick; + int _ignore_reading; +}; + +static void _decode_dhtxx(DHTXXD_t *self) +{ +/* + +-------+-------+ + | DHT11 | DHTXX | + +-------+-------+ +Temp C| 0-50 |-40-125| + +-------+-------+ +RH% | 20-80 | 0-100 | + +-------+-------+ + + 0 1 2 3 4 + +------+------+------+------+------+ +DHT11 |check-| 0 | temp | 0 | RH% | + |sum | | | | | + +------+------+------+------+------+ +DHT21 |check-| temp | temp | RH% | RH% | +DHT22 |sum | LSB | MSB | LSB | MSB | +DHT33 | | | | | | +DHT44 | | | | | | + +------+------+------+------+------+ +*/ + uint8_t chksum; + float div; + float t, h; + int valid; + + self->_data.timestamp = lgu_time(); + + chksum = (self->_byte[1] + self->_byte[2] + + self->_byte[3] + self->_byte[4]) & 0xFF; + + valid = 0; + + if (chksum == self->_byte[0]) + { + if (self->model == DHT11) + { + if ((self->_byte[1] == 0) && (self->_byte[3] == 0)) + { + valid = 1; + + t = self->_byte[2]; + + if (t > 60.0) valid = 0; + + h = self->_byte[4]; + + if ((h < 10.0) || (h > 90.0)) valid = 0; + } + } + else if (self->model == DHTXX) + { + valid = 1; + + h = ((float)((self->_byte[4]<<8) + self->_byte[3]))/10.0; + + if (h > 110.0) valid = 0; + + if (self->_byte[2] & 128) div = -10.0; else div = 10.0; + + t = ((float)(((self->_byte[2]&127)<<8) + self->_byte[1])) / div; + + if ((t < -50.0) || (t > 135.0)) valid = 0; + } + else /* AUTO */ + { + valid = 1; + + /* Try DHTXX first. */ + + h = ((float)((self->_byte[4]<<8) + self->_byte[3]))/10.0; + + if (h > 110.0) valid = 0; + + if (self->_byte[2] & 128) div = -10.0; else div = 10.0; + + t = ((float)(((self->_byte[2]&127)<<8) + self->_byte[1])) / div; + + if ((t < -50.0) || (t > 135.0)) valid = 0; + + if (!valid) + { + /* If not DHTXX try DHT11. */ + + if ((self->_byte[1] == 0) && (self->_byte[3] == 0)) + { + valid = 1; + + t = self->_byte[2]; + + if (t > 60.0) valid = 0; + + h = self->_byte[4]; + + if ((h < 10.0) || (h > 90.0)) valid = 0; + } + } + } + + if (valid) + { + self->_data.temperature = t; + self->_data.humidity = h; + self->_data.status = DHT_GOOD; + self->_new_reading = 1; + self->_ready = 1; + self->_bits = 0; + if (self->cb) (self->cb)(self->_data); + } + else + { + self->_data.status = DHT_BAD_DATA; + } + } + else + { + self->_data.status = DHT_BAD_CHECKSUM; + } +} + +static void _rising_edge( + int sbc, int chip, int gpio, int level, uint64_t tick, void *user) +{ + DHTXXD_t *self=user; + uint64_t edge_len; + + if (level != LG_TIMEOUT) + { + edge_len = tick - self->_last_edge_tick; + self->_last_edge_tick = tick; + + if (edge_len > 2e8) // 0.2 seconds + { + self->_bits = 0; + self->_code = 0; + } + else + { + self->_code <<= 1; + + if (edge_len > 1e5) // 100 microseconds, so a high bit + { + /* 1 bit */ + self->_code |= 1; + } + + self->_bits ++; + } + } + else + { + if (self->_bits >= 30) + { + if (!self->_ignore_reading) _decode_dhtxx(self); + + self->_bits = 0; + self->_code = 0; + } + } +} + +static void _trigger(DHTXXD_t *self) +{ + gpio_claim_output(self->sbc, self->chip, 0, self->gpio, 0); + + if (self->model != DHTXX) lgu_sleep(0.018); else lgu_sleep(0.001); + + gpio_claim_alert(self->sbc, self->chip, 0, RISING_EDGE, self->gpio, -1); +} + +static void *pthTriggerThread(void *x) +{ + DHTXXD_t *self=x; + float seconds; + + seconds = self->seconds; + + while (1) + { + if (seconds > 0.0) + { + if (seconds >= 30.0) + { + lgu_sleep(seconds - 4.0); + self->_ignore_reading = 1; + _trigger(self); + lgu_sleep(4.0); + self->_ignore_reading = 0; + } + else lgu_sleep(seconds); + + DHTXXD_manual_read(self); + } + else lgu_sleep(1); + } + pthread_exit(NULL); +} + +/* PUBLIC ----------------------------------------------------------------- */ + +DHTXXD_t *DHTXXD(int sbc, int chip, int gpio, int model, DHTXXD_CB_t cb_func) +{ + DHTXXD_t *self; + + self = malloc(sizeof(DHTXXD_t)); + + if (!self) return NULL; + + self->sbc = sbc; + self->chip = chip; + self->gpio = gpio; + self->model = model; + self->seconds = 0; + self->cb = cb_func; + + self->_data.sbc = sbc; + self->_data.gpio = gpio; + self->_data.status = 0; + self->_data.temperature = 0.0; + self->_data.humidity = 0.0; + + self->_ignore_reading = 0; + + self->_pth = NULL; + + self->_bits = 0; + + self->_ready = 0; + self->_new_reading = 0; + + self->_last_edge_tick = 0; + + gpio_set_watchdog_time(sbc, chip, gpio, 1000); // 1 ms watchdog + + self->_cb_id = callback(sbc, chip, gpio, RISING_EDGE, _rising_edge, self); + + return self; +} + +void DHTXXD_cancel(DHTXXD_t *self) +{ + if (self) + { + if (self->_pth) + { + thread_stop(self->_pth); + self->_pth = NULL; + } + + if (self->_cb_id >= 0) + { + callback_cancel(self->_cb_id); + self->_cb_id = -1; + } + free(self); + } +} +int DHTXXD_ready(DHTXXD_t *self) +{ + /* + Returns True if a new unread code is ready. + */ + return self->_ready; +} + +DHTXXD_data_t DHTXXD_data(DHTXXD_t *self) +{ + /* + Returns the last reading. + */ + self->_ready = 0; + return self->_data; +} + +void DHTXXD_manual_read(DHTXXD_t *self) +{ + int i; + double timestamp; + + self->_new_reading = 0; + self->_data.status = DHT_TIMEOUT; + + timestamp = lgu_time(); + + _trigger(self); + + /* timeout if no new reading */ + + for (i=0; i<4; i++) /* timeout after 0.2 seconds */ + { + lgu_sleep(0.05); + if (self->_new_reading) break; + } + + if (!self->_new_reading) + { + self->_data.timestamp = timestamp; + self->_ready = 1; + + if (self->cb) (self->cb)(self->_data); + } +} + +void DHTXXD_auto_read(DHTXXD_t *self, float seconds) +{ + if (seconds != self->seconds) + { + /* Delete any existing timer thread. */ + if (self->_pth != NULL) + { + thread_stop(self->_pth); + self->_pth = NULL; + } + self->seconds = seconds; + } + + if (seconds > 0.0) self->_pth = thread_start(pthTriggerThread, self); +} + +void fatal(char *fmt, ...) +{ + char buf[128]; + va_list ap; + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + + fprintf(stderr, "%s\n", buf); + + fflush(stderr); + + exit(EXIT_FAILURE); +} + +void usage() +{ + fprintf(stderr, "\n" \ + "Usage: DHTXXD [OPTION] ...\n" \ + " -g value, gpio, 0-31, default 4\n" \ + " -i value, reading interval in seconds\n" \ + " 0=single reading, default 0\n" \ + " -m value, model 0=auto, 1=DHT11, 2=other, default auto\n" \ + " -h string, host name, default NULL\n" \ + " -p value, socket port, 1024-32000, default 8888\n" \ + "EXAMPLE\n" \ + "DHTXXD -g11 -i5\n" \ + " Read a DHT connected to GPIO 11 every 5 seconds.\n\n"); +} + +int optGPIO = 4; +char *optHost = NULL; +char *optPort = NULL; +int optModel = DHTAUTO; +int optInterval = 0; + +static uint64_t getNum(char *str, int *err) +{ + uint64_t val; + char *endptr; + + *err = 0; + val = strtoll(str, &endptr, 0); + if (*endptr) {*err = 1; val = -1;} + return val; +} + +static void initOpts(int argc, char *argv[]) +{ + int opt, err, i; + + while ((opt = getopt(argc, argv, "g:h:i:m:p:")) != -1) + { + switch (opt) + { + case 'g': + i = getNum(optarg, &err); + if ((i >= 0) && (i <= 31)) optGPIO = i; + else fatal("invalid -g option (%d)", i); + break; + + case 'h': + optHost = malloc(sizeof(optarg)+1); + if (optHost) strcpy(optHost, optarg); + break; + + case 'i': + i = getNum(optarg, &err); + if ((i>=0) && (i<=86400)) optInterval = i; + else fatal("invalid -i option (%d)", i); + break; + + case 'm': + i = getNum(optarg, &err); + if ((i >= DHTAUTO) && (i <= DHTXX)) optModel = i; + else fatal("invalid -m option (%d)", i); + break; + + case 'p': + optPort = malloc(sizeof(optarg)+1); + if (optPort) strcpy(optPort, optarg); + break; + + default: /* '?' */ + usage(); + exit(-1); + } + } +} + +void cbf(DHTXXD_data_t r) +{ + printf("%d %.1f %.1f\n", r.status, r.temperature, r.humidity); +} + +int main(int argc, char *argv[]) +{ + int sbc; + int chip; + DHTXXD_t *dht; + + initOpts(argc, argv); + + sbc = rgpiod_start(optHost, optPort); /* Connect to local sbc. */ + + if (sbc >= 0) + { + /* open gpiochip 0 */ + + chip = gpiochip_open(sbc, 0); + + dht = DHTXXD(sbc, chip, optGPIO, optModel, cbf); /* Create DHTXX. */ + + if (optInterval) + { + DHTXXD_auto_read(dht, optInterval); + + while (1) lgu_sleep(60); + } + else + { + DHTXXD_manual_read(dht); + } + + DHTXXD_cancel(dht); /* Cancel DHTXX. */ + + gpiochip_close(sbc, chip); + + rgpiod_stop(sbc); /* Disconnect from local sbc. */ + } + else printf("can't connect to lgd\n"); + + return 0; +} + diff --git a/EXAMPLES/rgpio/errors.c b/EXAMPLES/rgpio/errors.c new file mode 100644 index 0000000..89efaa6 --- /dev/null +++ b/EXAMPLES/rgpio/errors.c @@ -0,0 +1,487 @@ +# +#include + +#include "lgpio.h" +#include "rgpio.h" + + +/* +gcc -Wall -o error error.c -lrgpio +./errors +*/ + +static int gFailOnly = 0; + +void check(char *func, int exp, int got) +{ + if (exp != got) + printf("FAIL: %s (expected %d, got %d)\n", func, exp, got); + else + if (!gFailOnly) printf("PASS: %s\n", func); +} + +void check_no_error(char *func, int got) +{ + if (got < 0) + printf("FAIL: %s (expected no error, got %d)\n", func, got); + else + if (!gFailOnly) printf("PASS: %s (%d)\n", func, got); +} + +void check_not_null(char *func, int got, char *name) +{ + if (got < 0) + printf("FAIL: %s (expected name, got %d)\n", func, got); + else + if (!gFailOnly) printf("PASS: %s (%s)\n", func, name); +} + +int main(int argc, char *argv[]) +{ + int sbc; + int status; + int h; + uint64_t temp64=0x123456789abcdef0; + uint64_t t64; + int xGpio[]={99, 88, 77, 66, 55, 44, 33, 22, 11}; + int xVals[]={11, 22, 33, 44, 55, 66, 77, 88, 99}; + uint32_t param[20]; + char buf[1024]; + lgPulse_t pulses[20]; + lgChipInfo_t chipInfo; + lgLineInfo_t lineInfo; + + if (argc > 1) gFailOnly =1; + + sbc = rgpiod_start(0, 0); + + if (sbc < 0) + { + fprintf(stderr, "initialisation failed (sbc, %d).\n", sbc); + return 1; + } + + printf("Connected to rgpiod (%d).\n", sbc); + + status = lgu_set_user(sbc, "admin", ".lg_secret"); + + status = lgu_set_internal(sbc, 0, 2); + + t64=0; + status = lgu_get_internal(sbc, 0, &t64); + check("lgu_get_internal", 3, t64); + + status = lgu_set_internal(sbc, 0, 3); + check("lgu_set_internal", LG_OKAY, status); + + status = lgu_set_user(sbc, "test1", ".lg_secret"); + + status = gpiochip_close(sbc, 9999); + check("gpiochip_close 1", LG_BAD_HANDLE, status); + + status = gpio_claim_input(sbc, 9999, 8888, 7777); + check("gpio_claim_input 1", LG_BAD_HANDLE, status); + + status = group_claim_input(sbc, 9999, 0, 5, xGpio); + check("group_claim_input 1", LG_BAD_HANDLE, status); + + status = gpio_claim_output(sbc, 9999, 8888, 7777, 6666); + check("gpio_claim_output 1", LG_BAD_HANDLE, status); + + status = group_claim_output(sbc, 9999, 8888, 7, xGpio, xVals); + check("group_claim_output 1", LG_BAD_HANDLE, status); + + status = gpio_write(sbc, 9999, 8888, 7777); + check("gpio_write 1", LG_BAD_HANDLE, status); + + status = group_read(sbc, 9999, 4, &temp64); + check("group_read 1", LG_BAD_HANDLE, status); + + status = group_write(sbc, 9999, 4, temp64, -1); + check("group_write 1", LG_BAD_HANDLE, status); + + status = gpiochip_open(sbc, 9999); + check("gpiochip_open 1", LG_CANNOT_OPEN_CHIP, status); + + status = gpio_read(sbc, 9999, 8888); + check("gpio_read 1", LG_BAD_HANDLE, status); + + status = gpio_free(sbc, 9999, 8888); + check("gpio_free 1", LG_BAD_HANDLE, status); + + status = group_free(sbc, 9999, 8888); + check("group_free 1", LG_BAD_HANDLE, status); + + status = tx_pulse(sbc, 9999, 8888, 7777, 6666, 5555, 4444); + check("tx_pulse 1", LG_BAD_HANDLE, status); + + status = tx_pwm(sbc, 9999, 8888, 7777, 6666, 5555, 4444); + check("tx_pwm 1", LG_BAD_PWM_DUTY, status); + + status = tx_pwm(sbc, 9999, 8888, 77777, 66, 5555, 4444); + check("tx_pwm 2", LG_BAD_PWM_FREQ, status); + + status = tx_pwm(sbc, 9999, 8888, 7777, 66, 5555, 4444); + check("tx_pwm 3", LG_BAD_HANDLE, status); + + status = tx_servo(sbc, 9999, 8888, 7777, 6666, 5555, 4444); + check("tx_servo 1", LG_BAD_SERVO_FREQ, status); + + status = tx_servo(sbc, 9999, 8888, 7777, 50, 5555, 4444); + check("tx_servo 2", LG_BAD_SERVO_WIDTH, status); + + status = tx_servo(sbc, 9999, 8888, 1500, 50, 5555, 4444); + check("tx_servo 3", LG_BAD_HANDLE, status); + + status = tx_room(sbc, 9999, 8888, 7777); + check("tx_room 1", LG_BAD_TX_TYPE, status); + + status = tx_room(sbc, 9999, 8888, 0); + check("tx_room 2", LG_BAD_HANDLE, status); + + status = tx_busy(sbc, 9999, 8888, 7777); + check("tx_busy 1", LG_BAD_TX_TYPE, status); + + status = tx_busy(sbc, 9999, 8888, 0); + check("tx_busy 2", LG_BAD_HANDLE, status); + + status = gpio_set_debounce_time(sbc, 9999, 8888, 10000000); + check("gpio_set_debounce_time 1", LG_BAD_DEBOUNCE_MICS, status); + + status = gpio_set_debounce_time(sbc, 9999, 8888, 1000000); + check("gpio_set_debounce_time 2", LG_BAD_HANDLE, status); + + status = gpio_set_watchdog_time(sbc, 9999, 8888, 1000000000); + check("gpio_set_watchdog_time 1", LG_BAD_WATCHDOG_MICS, status); + + status = gpio_set_watchdog_time(sbc, 9999, 8888, 1000000); + check("gpio_set_watchdog_time 2", LG_BAD_HANDLE, status); + + status = gpio_get_mode(sbc, 9999, 8888); + check("gpio_get_mode 1", LG_BAD_HANDLE, status); + + status = gpio_get_chip_info(sbc, 9999, &chipInfo); + check("gpio_get_chip_info 1", LG_BAD_HANDLE, status); + + status = gpio_get_line_info(sbc, 9999, 8888, &lineInfo); + check("gpio_get_line_info 1", LG_BAD_HANDLE, status); + + status = gpio_claim_alert(sbc, 9999, 8888, 7777, 6666, 5555); + check("gpio_claim_alert 1", LG_BAD_HANDLE, status); + + status = tx_wave(sbc, 9999, 8888, 0, pulses); + check("tx_wave 1", LG_BAD_HANDLE, status); + + status = file_close(sbc, 9999); + check("file_close", LG_BAD_HANDLE, status); + + status = file_list(sbc, "TEST/pattern/*", buf, sizeof(buf)); + check("file_list 1", LG_NO_FILE_MATCH, status); + + status = file_list(sbc, "./", buf, sizeof(buf)); + check("file_list 2", LG_NO_FILE_ACCESS, status); + + status = file_open(sbc, "TEST/file", 9999); + check("file_open 1", LG_BAD_FILE_MODE, status); + + status = file_open(sbc, "TEST/unlikelyFileName", 1); + check("file_open 2", LG_FILE_OPEN_FAILED, status); + + status = file_open(sbc, "unlikelyFileName", 1); + check("file_open 3", LG_FILE_OPEN_FAILED, status); + + status = file_read(sbc, 9999, buf, 0); + check("file_read 1", LG_BAD_FILE_PARAM, status); + + status = file_read(sbc, 9999, buf, sizeof(buf)); + check("file_read 2", LG_BAD_HANDLE, status); + + status = file_seek(sbc, 9999, 8888, 7777); + check("file_seek 1", LG_BAD_FILE_SEEK, status); + + status = file_seek(sbc, 9999, 8888, 1); + check("file_seek 2", LG_BAD_HANDLE, status); + + status = file_write(sbc, 9999, buf, 0); + check("file_write 1", LG_BAD_FILE_PARAM, status); + + status = file_write(sbc, 9999, buf, 5); + check("file_write 2", LG_BAD_HANDLE, status); + + status = i2c_close(sbc, 9999); + check("i2c_close", LG_BAD_HANDLE, status); + + status = i2c_open(sbc, 1, 8888, 7777); + check("i2c_open 1", LG_BAD_I2C_ADDR, status); + + status = i2c_open(sbc, 1, 0x40, 7777); + check("i2c_open 2", LG_BAD_I2C_FLAGS, status); + + status = i2c_open(sbc, 999, 0x40, 0); + check("i2c_open 3", LG_BAD_I2C_BUS, status); + + status = i2c_process_call(sbc, 9999, 8888, 7777); + check("i2c_process_call 1", LG_BAD_I2C_PARAM, status); + + status = i2c_process_call(sbc, 9999, 5, 77777); + check("i2c_process_call 2", LG_BAD_I2C_PARAM, status); + + status = i2c_process_call(sbc, 9999, 0xff, 0xffff); + check("i2c_process_call 3", LG_BAD_HANDLE, status); + + status = i2c_block_process_call(sbc, 9999, 256, buf, 10); + check("i2c_block_process_call 1", LG_BAD_I2C_PARAM, status); + + status = i2c_block_process_call(sbc, 9999, 23, buf, 0); + check("i2c_block_process_call 2", LG_BAD_I2C_PARAM, status); + + status = i2c_block_process_call(sbc, 9999, 23, buf, 10); + check("i2c_block_process_call 3", LG_BAD_HANDLE, status); + + status = i2c_read_byte_data(sbc, 9999, 8888); + check("i2c_read_byte_data 1", LG_BAD_I2C_PARAM, status); + + status = i2c_read_byte_data(sbc, 9999, 250); + check("i2c_read_byte_data 2", LG_BAD_HANDLE, status); + + status = i2c_read_device(sbc, 9999, buf, 0); + check("i2c_read_device 1", LG_BAD_I2C_PARAM, status); + + status = i2c_read_device(sbc, 9999, buf, 250); + check("i2c_read_device 2", LG_BAD_HANDLE, status); + + status = i2c_read_i2c_block_data(sbc, 9999, 8888, buf, 20); + check("i2c_read_i2c_block_data 1", LG_BAD_I2C_PARAM, status); + + status = i2c_read_i2c_block_data(sbc, 9999, 250, buf, 33); + check("i2c_read_i2c_block_data 2", LG_BAD_I2C_PARAM, status); + + status = i2c_read_i2c_block_data(sbc, 9999, 250, buf, 30); + check("i2c_read_i2c_block_data 3", LG_BAD_HANDLE, status); + + status = i2c_read_block_data(sbc, 9999, 8888, buf); + check("i2c_read_block_data 1", LG_BAD_I2C_PARAM, status); + + status = i2c_read_block_data(sbc, 9999, 25, buf); + check("i2c_read_block_data 2", LG_BAD_HANDLE, status); + + status = i2c_read_byte(sbc, 9999); + check("i2c_read_byte 1", LG_BAD_HANDLE, status); + + status = i2c_read_word_data(sbc, 9999, 8888); + check("i2c_read_word_data 1", LG_BAD_I2C_PARAM, status); + + status = i2c_read_word_data(sbc, 9999, 88); + check("i2c_read_word_data 2", LG_BAD_HANDLE, status); + + status = i2c_write_byte_data(sbc, 9999, 8888, 7777); + check("i2c_write_byte_data 1", LG_BAD_I2C_PARAM, status); + + status = i2c_write_byte_data(sbc, 9999, 8, 777); + check("i2c_write_byte_data 2", LG_BAD_I2C_PARAM, status); + + status = i2c_write_byte_data(sbc, 9999, 8, 77); + check("i2c_write_byte_data 3", LG_BAD_HANDLE, status); + + status = i2c_write_device(sbc, 9999, buf, 0); + check("i2c_write_device 1", LG_BAD_I2C_PARAM, status); + + status = i2c_write_device(sbc, 9999, buf, 20); + check("i2c_write_device 2", LG_BAD_HANDLE, status); + + status = i2c_write_i2c_block_data(sbc, 9999, 8888, buf, 20); + check("i2c_write_i2c_block_data 1", LG_BAD_I2C_PARAM, status); + + status = i2c_write_i2c_block_data(sbc, 9999, 88, buf, 0); + check("i2c_write_i2c_block_data 2", LG_BAD_I2C_PARAM, status); + + status = i2c_write_i2c_block_data(sbc, 9999, 88, buf, 10); + check("i2c_write_i2c_block_data 3", LG_BAD_HANDLE, status); + + status = i2c_write_block_data(sbc, 9999, 256, buf, 10); + check("i2c_write_block_data 1", LG_BAD_I2C_PARAM, status); + + status = i2c_write_block_data(sbc, 9999, 55, buf, 0); + check("i2c_write_block_data 2", LG_BAD_I2C_PARAM, status); + + status = i2c_write_block_data(sbc, 9999, 55, buf, 10); + check("i2c_write_block_data 3", LG_BAD_HANDLE, status); + + status = i2c_write_quick(sbc, 9999, 2); + check("i2c_write_quick 1", LG_BAD_I2C_PARAM, status); + + status = i2c_write_quick(sbc, 9999, 1); + check("i2c_write_quick 2", LG_BAD_HANDLE, status); + + status = i2c_write_byte(sbc, 9999, 8888); + check("i2c_write_byte 1", LG_BAD_I2C_PARAM, status); + + status = i2c_write_byte(sbc, 9999, 255); + check("i2c_write_byte 2", LG_BAD_HANDLE, status); + + status = i2c_write_word_data(sbc, 9999, 8888, 7777); + check("i2c_write_word_data 1", LG_BAD_I2C_PARAM, status); + + status = i2c_write_word_data(sbc, 9999, 88, 77777); + check("i2c_write_word_data 2", LG_BAD_I2C_PARAM, status); + + status = i2c_write_word_data(sbc, 9999, 88, 7777); + check("i2c_write_word_data 3", LG_BAD_HANDLE, status); + + status = i2c_zip(sbc, 9999, buf, 0, buf, 10); + check("i2c_zip 1", LG_BAD_POINTER, status); + + status = i2c_zip(sbc, 9999, buf, 10, buf, 10); + check("i2c_zip 2", LG_BAD_HANDLE, status); + + status = lgu_get_sbc_name(sbc, buf, sizeof(buf)); + check_not_null("lgu_get_sbc_name", status, buf); + + status = lgu_rgpio_version(); + check_no_error("lgu_rgpio_version", status); + + lgu_set_user(sbc, "test2", ".lg_secret"); + + h = notify_open(sbc); + check_no_error("notify_open 1", h); + + status = notify_resume(sbc, h); + check("notify_resume 1", LG_OKAY, status); + + status = notify_resume(sbc, 9999); + check("notify_resume 2", LG_BAD_HANDLE, status); + + status = notify_pause(sbc, h); + check("notify_pause 1", LG_OKAY, status); + + status = notify_pause(sbc, 9999); + check("notify_pause 2", LG_BAD_HANDLE, status); + + status = notify_close(sbc, h); + check("notify_close 1", LG_OKAY, status); + + status = notify_close(sbc, 9999); + check("notify_close 2", LG_BAD_HANDLE, status); + + lgu_set_user(sbc, "test3", ".lg_secret"); + + h = script_store(sbc, "tag 0 dcr p1 mils 100 jmp 0"); + check_no_error("script_store 1", h); + + while (script_status(sbc, h, param) == LG_SCRIPT_INITING) ; + + status = script_status(sbc, h, param); + check("script_status 1", LG_SCRIPT_READY, status); + + status = script_status(sbc, 9999, param); + check("script_status 2", LG_BAD_HANDLE, status); + + status = script_update(sbc, h, 4, param); + check("script_update 1", LG_OKAY, status); + + status = script_update(sbc, 9999, 4, param); + check("script_update 2", LG_BAD_HANDLE, status); + + status = script_run(sbc, h, 6, param); + check("script_run 1", LG_OKAY, status); + + status = script_run(sbc, 8888, 6, param); + check("script_run 2", LG_BAD_HANDLE, status); + + status = script_stop(sbc, h); + check("script_stop 1", LG_OKAY, status); + + status = script_stop(sbc, 9999); + check("script_stop 2", LG_BAD_HANDLE, status); + + status = script_delete(sbc, h); + check("script_delete 1", LG_OKAY, status); + + status = script_delete(sbc, 9999); + check("script_delete 2", LG_BAD_HANDLE, status); + + status = lgu_set_user(sbc, "test3", ".lg_secret"); + + status = serial_open(sbc, "raw", 9600, 0); + check("serial_open 1", LG_SERIAL_OPEN_FAILED, status); + + status = lgu_set_user(sbc, "test1", ".lg_secret"); + + status = serial_open(sbc, "ttyS0", 8888, 7777); + check("serial_open 2", LG_BAD_SERIAL_SPEED, status); + + status = serial_open(sbc, "ttyS0", 9600, 7777); + check("serial_open 3", LG_BAD_SERIAL_FLAGS, status); + + status = serial_open(sbc, "ttyUnlikelyFileName", 9600, 0); + check("serial_open 4", LG_SERIAL_OPEN_FAILED, status); + + status = serial_close(sbc, 9999); + check("serial_close 1", LG_BAD_HANDLE, status); + + status = serial_data_available(sbc, 9999); + check("serial_data_available 1", LG_BAD_HANDLE, status); + + status = serial_read(sbc, 9999, buf, 0); + check("serial_read 1", LG_BAD_SERIAL_PARAM, status); + + status = serial_read(sbc, 9999, buf, 8888); + check("serial_read 2", LG_BAD_HANDLE, status); + + status = serial_read_byte(sbc, 9999); + check("serial_read_byte 1", LG_BAD_HANDLE, status); + + status = serial_write(sbc, 9999, buf, 0); + check("serial_write 1", LG_BAD_SERIAL_PARAM, status); + + status = serial_write(sbc, 9999, buf, 8); + check("serial_write 2", LG_BAD_HANDLE, status); + + status = serial_write_byte(sbc, 9999, 256); + check("serial_write_byte 2", LG_BAD_SERIAL_PARAM, status); + + status = serial_write_byte(sbc, 9999, 88); + check("serial_write_byte 2", LG_BAD_HANDLE, status); + + status = lgu_set_user(sbc, "test3", ".lg_secret"); + + status = shell(sbc, "echo", "me"); + check("shell 1", 32512, status); + + status = lgu_set_user(sbc, "test1", ".lg_secret"); + + status = spi_close(sbc, 9999); + check("spi_close 1", LG_BAD_HANDLE, status); + + status = spi_open(sbc, 2, 1, 7777, 6666); + check("spi_open 1", LG_SPI_OPEN_FAILED, status); + + status = spi_read(sbc, 9999, buf, 0); + check("spi_read 1", LG_BAD_SPI_COUNT, status); + + status = spi_read(sbc, 9999, buf, 8888); + check("spi_read 2", LG_BAD_HANDLE, status); + + status = spi_write(sbc, 9999, buf, 0); + check("spi_write 1", LG_BAD_SPI_COUNT, status); + + status = spi_write(sbc, 9999, buf, 8); + check("spi_write 2", LG_BAD_HANDLE, status); + + status = spi_xfer(sbc, 9999, buf, buf, 0); + check("spi_xfer 1", LG_BAD_SPI_COUNT, status); + + status = spi_xfer(sbc, 9999, buf, buf, 10); + check("spi_xfer 2", LG_BAD_HANDLE, status); + + status = lgu_set_share_id(sbc, 9999, 8888); + check("lgu_set_share_id 1", LG_BAD_HANDLE, status); + + status = lgu_use_share_id(sbc, 0); + check("lgu_use_share_id 1", LG_OKAY, status); + + rgpiod_stop(sbc); + + return 0; +} + diff --git a/EXAMPLES/rgpio/files.c b/EXAMPLES/rgpio/files.c new file mode 100644 index 0000000..8b08cfb --- /dev/null +++ b/EXAMPLES/rgpio/files.c @@ -0,0 +1,145 @@ +#include +#include + +#include +#include + +/* +gcc -Wall -o files files.c -lrgpio + +./files +*/ + +int sbc; +int h; +int s; +int i; +int mode; +int fails; +char data[2048]; + +void check(char *func, int exp, int got) +{ + if (exp != got) + printf("FAIL: %s (expected %d, got %d)\n", func, exp, got); + else + printf("PASS: %s\n", func); +} + +void check_no_error(char *func, int got) +{ + if (got < 0) + printf("FAIL: %s (expected no error, got %d)\n", func, got); + else + printf("PASS: %s (%d)\n", func, got); +} + +void check_error(char *func, int got) +{ + if (got < 0) + printf("PASS: %s (%d)\n", func, got); + else + printf("FAIL: %s (expected error, got %d)\n", func, got); +} + +void check_not_null(char *func, int got) +{ + if (got == 0) + printf("FAIL: %s (expected name, got %d)\n", func, got); + else + printf("PASS: %s (%d)\n", func, got); +} + +int main(int argc, char *argv[]) +{ + sbc = rgpiod_start(NULL, NULL); + + if (sbc < 0) + { + printf("connection failed\n"); + exit(-1); + } + + // open file for write, create if it doesn't exist + + h = file_open(sbc, + "test.file", LG_FILE_WRITE+LG_FILE_CREATE+LG_FILE_TRUNC); + check_no_error("file_open", h); + + // write some text + + s = file_write(sbc, h, "Now is the winter of our discontent\n", 36); + check_no_error("file_write", s); + + s = file_write(sbc, h, "Made glorious summer by this son of York\n", 41); + check_no_error("file_write", s); + + s = file_close(sbc, h); + check_no_error("file_close", s); + + // open for read + + h = file_open(sbc, "test.file", LG_FILE_READ); + check_no_error("file_open", h); + + s = file_read(sbc, h, data, 100); // read up to 100 characters from file + check("file_read", 77, s); + + s = file_seek(sbc, h, 25, LG_FROM_START); + check_no_error("file_seek", s); + + s = file_read(sbc, h, data, 100); // read up to 100 characters from file + check("file_read", 52, s); + + s = file_seek(sbc, h, -25, LG_FROM_END); + check_no_error("file_seek", s); + + s = file_read(sbc, h, data, 100); // read up to 100 charactrs from file + check("file_read", 25, s); + + s = file_seek(sbc, h, -50, LG_FROM_END); + check_no_error("file_seek", s); + + s = file_read(sbc, h, data, 5); // read up to 5 charactrs from file + check("file_read", 5, s); + + file_seek(sbc, h, -20, LG_FROM_CURRENT); + check_no_error("file_seek", s); + + s = file_read(sbc, h, data, 100); // read up to 100 charactrs from file + check("file_read", 65, s); + + s = file_close(sbc, h); + check_no_error("file_close", s); + + s = file_list(sbc, "test*", data, 1000); + check_no_error("file_list", s); + + char *pat2[]={"/tmp/unlikely-file-name", "file34"}; + + for (i=0; i +#include + +#include +#include + +#define OUT 21 +#define LOOPS 120 + +#define LFLAGS 0 + +int main(int argc, char *argv[]) +{ + int sbc; + int h; + int i; + double start, end; + + sbc = rgpiod_start(NULL, NULL); + + if (sbc < 0) + { + printf("connection failed\n"); + exit(-1); + } + + h = gpiochip_open(sbc, 0); + + if (h >= 0) + { + if (gpio_claim_output(sbc, h, LFLAGS, OUT, 0) == LG_OKAY) + { + tx_pulse(sbc, h, OUT, 20000, 30000, 0, 0); + + lgu_sleep(2); + + tx_pulse(sbc, h, OUT, 20000, 5000, 0, LOOPS); + + start = lgu_time(); + + while (tx_busy(sbc, h, OUT, LG_TX_PWM)) lgu_sleep(0.01); + + end = lgu_time(); + + printf("%d cycles at 40 Hz took %.1f seconds\n", LOOPS, end-start); + } + + gpiochip_close(sbc, h); + } + + rgpiod_stop(sbc); +} + diff --git a/EXAMPLES/rgpio/tx_wave.c b/EXAMPLES/rgpio/tx_wave.c new file mode 100644 index 0000000..3025e72 --- /dev/null +++ b/EXAMPLES/rgpio/tx_wave.c @@ -0,0 +1,66 @@ +#include +#include + +#include +#include + +int OUT[6]={20, 21, 22, 23, 24, 25}; +int lvl[6]={ 0, 0, 0, 0, 0, 0}; + +#define PULSES 500 + +#define LFLAGS 0 + +lgPulse_t pulses[PULSES]; + +int main(int argc, char *argv[]) +{ + int sbc; + int h; + int i; + double start, end; + int delay = 1000; + int total = 0; + + sbc = rgpiod_start(NULL, NULL); + + if (sbc < 0) + { + printf("connection failed\n"); + exit(-1); + } + + for (i=0; i= 0) + { + if (group_claim_output(sbc, h, LFLAGS, 6, OUT, lvl) == LG_OKAY) + { + tx_wave(sbc, h, OUT[0], PULSES, pulses); + + start = lgu_time(); + + while (tx_busy(sbc, h, OUT[0], LG_TX_WAVE)) lgu_sleep(0.01); + + end = lgu_time(); + + printf("%d pulses took %.1f seconds (exp=%.1f)\n", + PULSES, end-start, total/1e6); + } + + gpiochip_close(sbc, h); + } + + rgpiod_stop(sbc); +} + diff --git a/EXAMPLES/rgs/files b/EXAMPLES/rgs/files new file mode 100755 index 0000000..3177480 --- /dev/null +++ b/EXAMPLES/rgs/files @@ -0,0 +1,11 @@ +#!/bin/bash + +h=$(lgs fo TEST/access 1) +echo "h=$h" +r=$(lgs fr $h 1000 -a) +echo "r=$r" +s=$(lgs fc $h) +echo "s=$s" +s=$(lgs fc $h) +echo "s=$s" + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..fb5c514 --- /dev/null +++ b/Makefile @@ -0,0 +1,199 @@ +# +# Set CROSS_PREFIX to prepend to all compiler tools at once for easier +# cross-compilation. +CROSS_PREFIX = +CC = $(CROSS_PREFIX)gcc +AR = $(CROSS_PREFIX)ar +RANLIB = $(CROSS_PREFIX)ranlib +SIZE = $(CROSS_PREFIX)size +STRIP = $(CROSS_PREFIX)strip +SHLIB = $(CC) -shared +STRIPLIB = $(STRIP) --strip-unneeded + +SOVERSION = 1 + +prefix = /usr/local +exec_prefix = $(prefix) +bindir = $(exec_prefix)/bin +includedir = $(prefix)/include +libdir = $(prefix)/lib +mandir = $(prefix)/man + +CFLAGS += -O3 -Wall -pthread -fpic +#CFLAGS += -O0 -g -Wall -pthread -fpic + +# -Wunused-local-typedefs -Wunused-macros -fno-common + +LIB_LGPIO = liblgpio.so +LIB_RGPIO = librgpio.so + +OBJ_LGPIO = \ + lgCtx.o \ + lgDbg.o \ + lgErr.o \ + lgGpio.o \ + lgHdl.o \ + lgI2C.o \ + lgNotify.o \ + lgPthAlerts.o \ + lgPthTx.o \ + lgSerial.o \ + lgSPI.o \ + lgThread.o \ + lgUtil.o \ + +OBJ_RGPIO = \ + rgpio.o \ + lgCfg.o \ + lgErr.o \ + lgDbg.o \ + lgMD5.o \ + +OBJ_RGPIOD = \ + lgCfg.o \ + lgCmd.o \ + lgExec.o \ + lgFile.o \ + lgMD5.o \ + lgPthSocket.o \ + lgScript.o \ + +OBJ_RGS = \ + lgCmd.o \ + lgCfg.o \ + lgErr.o \ + lgMD5.o \ + +DOCS = \ + DOC/src/defs/rgs.def \ + DOC/src/defs/rgpiod.def \ + DOC/src/defs/permits.def \ + DOC/src/defs/scripts.def \ + lgpio.h \ + rgpio.h \ + +LIB = $(LIB_LGPIO) $(LIB_RGPIO) + +ALL = $(LIB) rgpiod rgs DOC/.docs + +LINK_LGPIO = -L. -llgpio -pthread -lrt +LINK_RGPIO = -L. -lrgpio -pthread -lrt + +all: $(ALL) + +lib: $(LIB) + +rgpio.o: rgpio.c lgpio.h lgCmd.h rgpio.h + $(CC) $(CFLAGS) -c -o rgpio.o rgpio.c + +rgpiod: rgpiod.o $(OBJ_RGPIOD) $(LIB_LGPIO) + $(CC) -o rgpiod rgpiod.o $(OBJ_RGPIOD) $(LINK_LGPIO) + $(STRIP) rgpiod + +rgs: rgs.o $(OBJ_RGS) + $(CC) -o rgs rgs.o $(OBJ_RGS) + $(STRIP) rgs + +DOC/.docs: $(DOCS) + @[ -d "DOC" ] && cd DOC && ./cdoc || echo "*** No DOC directory ***" + touch DOC/.docs + +clean: + rm -f *.o *.i *.s *~ $(ALL) *.so.$(SOVERSION) + +ifeq ($(DESTDIR),) + PYINSTALLARGS = +else + PYINSTALLARGS = --root=$(DESTDIR) +endif + +html: $(ALL) + @[ -d "DOC" ] && cd DOC && ./makedoc || echo "*** No DOC directory ***" + +c: $(ALL) + @install -m 0755 -d $(DESTDIR)$(includedir) + install -m 0644 lgpio.h $(DESTDIR)$(includedir) + install -m 0644 rgpio.h $(DESTDIR)$(includedir) + @install -m 0755 -d $(DESTDIR)$(libdir) + install -m 0755 liblgpio.so.$(SOVERSION) $(DESTDIR)$(libdir) + install -m 0755 librgpio.so.$(SOVERSION) $(DESTDIR)$(libdir) + @cd $(DESTDIR)$(libdir) && ln -fs liblgpio.so.$(SOVERSION) liblgpio.so + @cd $(DESTDIR)$(libdir) && ln -fs librgpio.so.$(SOVERSION) librgpio.so + @install -m 0755 -d $(DESTDIR)$(bindir) + install -m 0755 rgpiod $(DESTDIR)$(bindir) + install -m 0755 rgs $(DESTDIR)$(bindir) + @install -m 0755 -d $(DESTDIR)$(mandir)/man1 + install -m 0644 rgpiod.1 $(DESTDIR)$(mandir)/man1 + install -m 0644 rgs.1 $(DESTDIR)$(mandir)/man1 + @install -m 0755 -d $(DESTDIR)$(mandir)/man3 + install -m 0644 lgpio.3 $(DESTDIR)$(mandir)/man3 + install -m 0644 rgpio.3 $(DESTDIR)$(mandir)/man3 +ifeq ($(DESTDIR),) + ldconfig +endif + +python: $(ALL) + @if which python2; then cd PY_RGPIO && python2 setup.py -q install $(PYINSTALLARGS) || echo "*** install of Python2 rgpio.py failed ***"; fi + @if which python3; then cd PY_RGPIO && python3 setup.py -q install $(PYINSTALLARGS) || echo "*** install of Python3 rgpio.py failed ***"; fi + @if which swig; then cd PY_LGPIO && swig -python lgpio.i || echo "*** need swig package to install lgpio.py ***"; fi + @if which swig python2; then cd PY_LGPIO && python2 setup.py -q install $(PYINSTALLARGS) || echo "*** install of Python2 lgpio.py failed ***"; fi + @if which swig python3; then cd PY_LGPIO && python3 setup.py -q install $(PYINSTALLARGS) || echo "*** install of Python3 lgpio.py failed ***"; fi + +uninstall: + rm -f $(DESTDIR)$(includedir)/lgpio.h + rm -f $(DESTDIR)$(includedir)/rgpio.h + rm -f $(DESTDIR)$(libdir)/liblgpio.so + rm -f $(DESTDIR)$(libdir)/librgpio.so + rm -f $(DESTDIR)$(libdir)/liblgpio.so.$(SOVERSION) + rm -f $(DESTDIR)$(libdir)/librgpio.so.$(SOVERSION) + rm -f $(DESTDIR)$(bindir)/rgpiod + rm -f $(DESTDIR)$(bindir)/rgs + rm -f $(DESTDIR)$(mandir)/man1/rgpiod.1 + rm -f $(DESTDIR)$(mandir)/man1/rgs.1 + rm -f $(DESTDIR)$(mandir)/man3/lgpio.3 + rm -f $(DESTDIR)$(mandir)/man3/rgpio.3 +ifeq ($(DESTDIR),) + ldconfig +endif + +$(LIB_LGPIO): $(OBJ_LGPIO) + $(SHLIB) -pthread -Wl,-soname,$(LIB_LGPIO).$(SOVERSION) -o $(LIB_LGPIO).$(SOVERSION) $(OBJ_LGPIO) + ln -fs $(LIB_LGPIO).$(SOVERSION) $(LIB_LGPIO) + $(STRIPLIB) $(LIB_LGPIO) + $(SIZE) $(LIB_LGPIO) + +$(LIB_RGPIO): $(OBJ_RGPIO) + $(SHLIB) -pthread -Wl,-soname,$(LIB_RGPIO).$(SOVERSION) -o $(LIB_RGPIO).$(SOVERSION) $(OBJ_RGPIO) + ln -fs $(LIB_RGPIO).$(SOVERSION) $(LIB_RGPIO) + $(STRIPLIB) $(LIB_RGPIO) + $(SIZE) $(LIB_RGPIO) + +# generated using gcc -MM *.c + +lgCfg.o: lgCfg.c lgCfg.h +lgCmd.o: lgCmd.c lgpio.h rgpiod.h lgCmd.h +lgCtx.o: lgCtx.c lgpio.h lgDbg.h lgCtx.h +lgDbg.o: lgDbg.c lgpio.h lgDbg.h +lgErr.o: lgErr.c lgpio.h +lgExec.o: lgExec.c lgpio.h rgpiod.h lgCmd.h lgCfg.h lgCtx.h lgDbg.h \ + lgHdl.h lgMD5.h +lgFile.o: lgFile.c lgpio.h rgpiod.h lgCmd.h lgDbg.h lgHdl.h +lgGpio.o: lgGpio.c lgpio.h lgDbg.h lgGpio.h lgHdl.h lgPthAlerts.h \ + lgPthTx.h +lgHdl.o: lgHdl.c lgpio.h lgCtx.h lgDbg.h lgHdl.h +lgI2C.o: lgI2C.c lgpio.h lgDbg.h lgHdl.h +lgMD5.o: lgMD5.c lgpio.h lgMD5.h lgCfg.h +lgNotify.o: lgNotify.c lgpio.h lgDbg.h lgHdl.h +lgPthAlerts.o: lgPthAlerts.c lgDbg.h lgHdl.h lgpio.h lgGpio.h \ + lgPthAlerts.h +lgPthSocket.o: lgPthSocket.c lgpio.h rgpiod.h lgCmd.h lgCtx.h lgDbg.h \ + lgHdl.h +lgPthTx.o: lgPthTx.c lgDbg.h lgHdl.h lgpio.h lgPthTx.h lgGpio.h +lgScript.o: lgScript.c lgpio.h rgpiod.h lgCmd.h lgCtx.h lgDbg.h lgHdl.h +lgSerial.o: lgSerial.c lgpio.h lgDbg.h lgHdl.h +lgSPI.o: lgSPI.c lgpio.h lgDbg.h lgHdl.h +lgThread.o: lgThread.c lgpio.h lgDbg.h +lgUtil.o: lgUtil.c lgpio.h lgDbg.h +rgpio.o: rgpio.c rgpiod.h lgCmd.h lgpio.h rgpio.h lgCfg.h lgMD5.h +rgpiod.o: rgpiod.c lgpio.h rgpiod.h lgCmd.h lgDbg.h +rgs.o: rgs.c lgpio.h rgpiod.h lgCmd.h lgMD5.h diff --git a/PY_LGPIO/lgpio.i b/PY_LGPIO/lgpio.i new file mode 100644 index 0000000..b51f69f --- /dev/null +++ b/PY_LGPIO/lgpio.i @@ -0,0 +1,749 @@ +#if defined(SWIGPYTHON) + +%define lgpio_docstr +" +[http://abyz.me.uk/lg/py_lgpio.html] + +lgpio is a Python module which allows control of the GPIO +of a Linux SBC. + +*Features* + +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 + +*Exceptions* + +By default a fatal exception is raised if you pass an invalid +argument to a lgpio function. + +If you wish to handle the returned status yourself you should set +lgpio.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: + +... +lgpio.exceptions = False + +# Code where you want to test the error status + +lgpio.exceptions = True +... + +*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 + +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 + +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 + +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_internal Get an internal configuration value +set_internal Set an internal configuration value + +get_module_version Get the lgpio Python module version +error_text Get the error text for an error code +" +%enddef + +%module (docstring = lgpio_docstr) lgpio + +%{ +#include "lgpio.h" +%} + +%include "typemaps.i" +%include "stdint.i" +%include "pybuffer.i" + +%pythoncode "lgpio_extra.py" + +// lgGroupClaimInput +// lgGroupClaimOutput +%typemap(in) (int count, const int *gpios) +{ + int res; + Py_buffer view; + + if (!PyObject_CheckBuffer($input)) + { + PyErr_SetString(PyExc_ValueError, "Expecting a buffer object"); + SWIG_fail; + } + + res = PyObject_GetBuffer($input, &view, PyBUF_CONTIG_RO); + $1 = view.len/4; + $2 = view.buf; + PyBuffer_Release(&view); + + if (res < 0) + { + PyErr_SetString(PyExc_ValueError, "Odd buffer object"); + SWIG_fail; + } + + if (($1 < 1) || ($1 > 64)) + { + PyErr_SetString(PyExc_ValueError,"Expecting 1-64 GPIO"); + SWIG_fail; + } +} + +// lgGroupClaimOutput +%typemap(in) (const int *levels) +{ + int res; + Py_buffer view; + + if (!PyObject_CheckBuffer($input)) + { + PyErr_SetString(PyExc_ValueError, "Expecting a buffer object"); + SWIG_fail; + } + + res = PyObject_GetBuffer($input, &view, PyBUF_CONTIG_RO); + $1 = view.buf; + PyBuffer_Release(&view); + + if (res < 0) + { + PyErr_SetString(PyExc_ValueError, "Odd buffer object"); + SWIG_fail; + } +} + + +// lgTxWave +%typemap(in) (int count, lgPulse_p pulses) +{ + int res; + Py_buffer view; + + if (!PyObject_CheckBuffer($input)) + { + PyErr_SetString(PyExc_ValueError, "Expecting a buffer object"); + SWIG_fail; + } + + res = PyObject_GetBuffer($input, &view, PyBUF_CONTIG_RO); + $1 = view.len/24; + $2 = view.buf; + PyBuffer_Release(&view); + + if (res <0) + { + PyErr_SetString(PyExc_ValueError, "Odd buffer object"); + SWIG_fail; + } +} + +// lgI2cWriteBlockData +// lgI2cWriteI2CBlockData +// lgI2cWriteDevice +// lgSerialWrite +// lgSpiWrite +%typemap(in) (const char *txBuf, int count) +{ + int res; + Py_buffer view; + + if (!PyObject_CheckBuffer($input)) + { + PyErr_SetString(PyExc_ValueError, "Expecting a buffer object"); + SWIG_fail; + } + + res = PyObject_GetBuffer($input, &view, PyBUF_CONTIG_RO); + $1 = view.buf; + $2 = view.len; + PyBuffer_Release(&view); + + if (res <0) + { + PyErr_SetString(PyExc_ValueError, "Odd buffer object"); + SWIG_fail; + } +} + +// lgI2cReadI2CBlockData +// lgI2cReadDevice +// lgSerialRead +// lgSpiRead +%typemap(in) (char *rxBuf, int count) +{ + if (!PyInt_Check($input)) + { + PyErr_SetString(PyExc_ValueError, "Expecting an integer"); + SWIG_fail; + } + $2 = PyInt_AsLong($input); + if ($2 < 0) + { + PyErr_SetString(PyExc_ValueError, "Positive integer expected"); + SWIG_fail; + } + $1 = (void *) malloc($2); +} + +// lgI2cReadI2CBlockData +// lgI2cReadDevice +// lgSerialRead +// lgSpiRead +%typemap(argout) (char *rxBuf, int count) +{ + PyObject *o1, *o2; + Py_XDECREF($result); /* Blow away any previous result */ + $result = PyList_New(2); + o1 = SWIG_From_int((int)(result)); + PyList_SetItem($result, 0, o1); + if (result > 0) o2 = PyByteArray_FromStringAndSize($1, result); + else o2 = PyByteArray_FromStringAndSize("", 0); + PyList_SetItem($result, 1, o2); + free($1); +} + +// lgSpiXfer +%typemap(in) (const char *txBuf, char *rxBuf, int count) +{ + int res; + Py_buffer view; + + if (!PyObject_CheckBuffer($input)) + { + PyErr_SetString(PyExc_ValueError, "Expecting a buffer object"); + SWIG_fail; + } + + res = PyObject_GetBuffer($input, &view, PyBUF_CONTIG_RO); + $1 = view.buf; + $3 = view.len; + PyBuffer_Release(&view); + + if (res <0) + { + PyErr_SetString(PyExc_ValueError, "Odd buffer object"); + SWIG_fail; + } + + $2 = (void *) malloc($3); +} + +// lgSpiXfer +%typemap(argout) (const char *txBuf, char *rxBuf, int count) +{ + PyObject *o1, *o2; + Py_XDECREF($result); /* Blow away any previous result */ + + $result = PyList_New(2); + o1 = PyInt_FromLong(result); + PyList_SetItem($result, 0, o1); + + if (result > 0) o2 = PyByteArray_FromStringAndSize($2, result); + else o2 = PyByteArray_FromStringAndSize("", 0); + PyList_SetItem($result, 1, o2); + + free($2); +} + +// lgI2cBlockProcessCall +%typemap(in) (char *ioBuf, int count) (char rx32Buf[32]) +{ + int res; + char *buf; + int len; + int i; + Py_buffer view; + + if (!PyObject_CheckBuffer($input)) + { + PyErr_SetString(PyExc_ValueError, "Expecting a buffer object"); + SWIG_fail; + } + + res = PyObject_GetBuffer($input, &view, PyBUF_CONTIG_RO); + buf = view.buf; + len = view.len; + PyBuffer_Release(&view); + + if (res < 0) + { + PyErr_SetString(PyExc_ValueError, "Odd buffer object"); + SWIG_fail; + } + + if (len > 32) len = 32; + for (i=0; i 0) o2 = PyByteArray_FromStringAndSize($1, result); + else o2 = PyByteArray_FromStringAndSize("", 0); + PyList_SetItem($result, 1, o2); +} + +// lgI2cReadBlockData +%typemap(in, numinputs=0) (char *rx32Buf) (char rx32Buf[32]) +{ + $1 = &rx32Buf[0]; +} + +// lgI2cReadBlockData +%typemap(argout) (char *rx32Buf) +{ + PyObject *o1, *o2; + Py_XDECREF($result); /* Blow away any previous result */ + $result = PyList_New(2); + + o1 = PyInt_FromLong(result); + PyList_SetItem($result, 0, o1); + + if (result > 0) o2 = PyByteArray_FromStringAndSize($1, result); + else o2 = PyByteArray_FromStringAndSize("", 0); + PyList_SetItem($result, 1, o2); +} + +// lgI2cZip +%typemap(in) (const char *txBuf, int txCount, char *rxBuf, int rxCount) +{ + int res; + Py_buffer view; + + if (!PyObject_CheckBuffer($input)) + { + PyErr_SetString(PyExc_ValueError, "Expecting a buffer object"); + SWIG_fail; + } + + res = PyObject_GetBuffer($input, &view, PyBUF_CONTIG_RO); + $1 = view.buf; + $2 = view.len; + PyBuffer_Release(&view); + + if (res < 0) + { + PyErr_SetString(PyExc_ValueError, "Odd buffer object"); + SWIG_fail; + } + + $3 = (void *) malloc(1000); + $4 = 1000; +} + +// lgI2cZip +%typemap(argout) (const char *txBuf, int txCount, char *rxBuf, int rxCount) +{ + PyObject *o1, *o2; + Py_XDECREF($result); /* Blow away any previous result */ + $result = PyList_New(2); + o1 = SWIG_From_int((int)(result)); + PyList_SetItem($result, 0, o1); + if (result > 0) o2 = PyByteArray_FromStringAndSize($3, result); + else o2 = PyByteArray_FromStringAndSize("", 0); + PyList_SetItem($result, 1, o2); + free($3); +} + +// lgGpioGetChipInfo +%typemap(in, numinputs=0) (lgChipInfo_p chipInfp) (lgChipInfo_t chipInf) +{ + $1 = &chipInf; +} + +// lgGpioGetChipInfo +%typemap(argout) (lgChipInfo_p chipInfp) +{ + PyObject *o1, *o2, *o3, *o4; + Py_XDECREF($result); /* Blow away any previous result */ + $result = PyList_New(4); + + if (result >= 0) + { + result = 0; + + o2 = PyInt_FromLong(chipInf2.lines); + o3 = PyString_FromString(chipInf2.name); + o4 = PyString_FromString(chipInf2.label); + } + else + { + o2 = PyInt_FromLong(0); + o3 = PyString_FromString(""); + o4 = PyString_FromString(""); + } + + o1 = PyInt_FromLong(result); + + PyList_SetItem($result, 0, o1); + PyList_SetItem($result, 1, o2); + PyList_SetItem($result, 2, o3); + PyList_SetItem($result, 3, o4); +} + +// lgGpioGetLineInfo +%typemap(in, numinputs=0) (lgLineInfo_p lineInfp) (lgLineInfo_t lineInf) +{ + $1 = &lineInf; +} + +// lgGpioGetLineInfo +%typemap(argout) (lgLineInfo_p lineInfp) +{ + PyObject *o1, *o2, *o3, *o4, *o5; + Py_XDECREF($result); /* Blow away any previous result */ + $result = PyList_New(5); + + if (result >= 0) + { + result = 0; + + o2 = PyInt_FromLong(lineInf3.offset); + o3 = PyInt_FromLong(lineInf3.lFlags); + o4 = PyString_FromString(lineInf3.name); + o5 = PyString_FromString(lineInf3.user); + } + else + { + o2 = PyInt_FromLong(0); + o3 = PyInt_FromLong(0); + o4 = PyString_FromString(""); + o5 = PyString_FromString(""); + } + + o1 = PyInt_FromLong(result); + + PyList_SetItem($result, 0, o1); + PyList_SetItem($result, 1, o2); + PyList_SetItem($result, 2, o3); + PyList_SetItem($result, 3, o4); + PyList_SetItem($result, 4, o5); +} + +#else + #warning no typemaps defined +#endif + +%rename(_gpiochip_open) lgGpiochipOpen; +extern int lgGpiochipOpen(int gpioDev); + +%rename(_gpiochip_close) lgGpiochipClose; +extern int lgGpiochipClose(int handle); + +%rename(_gpio_get_chip_info) lgGpioGetChipInfo; +extern int lgGpioGetChipInfo(int handle, lgChipInfo_p chipInfp); + +%rename(_gpio_get_line_info) lgGpioGetLineInfo; +extern int lgGpioGetLineInfo(int handle, int gpio, lgLineInfo_p lineInfp); + +%rename(_gpio_get_mode) lgGpioGetMode; +extern int lgGpioGetMode(int handle, int gpio); + +%rename(_gpio_claim_input) lgGpioClaimInput; +extern int lgGpioClaimInput(int handle, int lFlags, int gpio); + +%rename(_gpio_claim_output) lgGpioClaimOutput; +extern int lgGpioClaimOutput(int handle, int lFlags, int gpio, int level); + +%rename(_gpio_claim_alert) lgGpioClaimAlert; +extern int lgGpioClaimAlert(int handle, int lFlags, int eFlags, int gpio, int nfyHandle); + +%rename(_gpio_free) lgGpioFree; +extern int lgGpioFree(int handle, int gpio); + +%rename(_group_claim_input) lgGroupClaimInput; +extern int lgGroupClaimInput(int handle, int lFlags, int count, const int *gpios); + +%rename(_group_claim_output) lgGroupClaimOutput; +extern int lgGroupClaimOutput(int handle, int lFlags, int count, const int *gpios, const int *levels); + +%rename(_group_free) lgGroupFree; +extern int lgGroupFree(int handle, int gpio); + +%rename(_gpio_read) lgGpioRead; +extern int lgGpioRead(int handle, int gpio); + +%rename(_gpio_write) lgGpioWrite; +extern int lgGpioWrite(int handle, int gpio, int level); + +%rename(_group_read) lgGroupRead; +extern int lgGroupRead(int handle, int gpio, uint64_t *OUTPUT); + +%rename(_group_write) lgGroupWrite; +extern int lgGroupWrite(int handle, int gpio, uint64_t groupBits, uint64_t groupMask); + +%rename(_tx_pulse) lgTxPulse; +extern int lgTxPulse(int handle, int gpio, int pulseOn, int pulseOff, int pulseOffset, int pulseCycles); + +%rename(_tx_pwm) lgTxPwm; +extern int lgTxPwm(int handle, int gpio, float pwmFrequency, float pwmDutyCycle, int pwmOffset, int pwmCycles); + +%rename(_tx_servo) lgTxServo; +extern int lgTxServo(int handle, int gpio, int pulseWidth, int servoFrequency, + int servoOffset, int servoCycles); + +%rename(_tx_wave) lgTxWave; +extern int lgTxWave(int handle, int gpio, int count, lgPulse_p pulses); + +%rename(_tx_busy) lgTxBusy; +extern int lgTxBusy(int handle, int gpio, int kind); + +%rename(_tx_room) lgTxRoom; +extern int lgTxRoom(int handle, int gpio, int kind); + +%rename(_gpio_set_debounce_micros) lgGpioSetDebounce; +extern int lgGpioSetDebounce(int handle, int gpio, int debounce_us); + +%rename(_gpio_set_watchdog_micros) lgGpioSetWatchdog; +extern int lgGpioSetWatchdog(int handle, int gpio, int watchdog_us); + +%rename(_notify_open) lgNotifyOpen; +extern int lgNotifyOpen(void); + +%rename(_notify_resume) lgNotifyResume; +extern int lgNotifyResume(int handle); + +%rename(_notify_pause) lgNotifyPause; +extern int lgNotifyPause(int handle); + +%rename(_notify_close) lgNotifyClose; +extern int lgNotifyClose(int handle); + +%rename(_i2c_open) lgI2cOpen; +extern int lgI2cOpen(int i2cDev, int i2cAddr, int i2cFlags); + +%rename(_i2c_close) lgI2cClose; +extern int lgI2cClose(int handle); + +%rename(_i2c_write_quick) lgI2cWriteQuick; +extern int lgI2cWriteQuick(int handle, int bitVal); + +%rename(_i2c_write_byte) lgI2cWriteByte; +extern int lgI2cWriteByte(int handle, int byteVal); + +%rename(_i2c_read_byte) lgI2cReadByte; +extern int lgI2cReadByte(int handle); + +%rename(_i2c_write_byte_data) lgI2cWriteByteData; +extern int lgI2cWriteByteData(int handle, int i2cReg, int byteVal); + +%rename(_i2c_WriteWordData) lgI2cWriteWordData; +extern int lgI2cWriteWordData(int handle, int i2cReg, int wordVal); + +%rename(_i2c_read_byte_data) lgI2cReadByteData; +extern int lgI2cReadByteData(int handle, int i2cReg); + +%rename(_i2c_read_word_data) lgI2cReadWordData; +extern int lgI2cReadWordData(int handle, int i2cReg); + +%rename(_i2c_process_call) lgI2cProcessCall; +extern int lgI2cProcessCall(int handle, int i2cReg, int wordVal); + + +%rename(_i2c_write_block_data) lgI2cWriteBlockData; +extern int lgI2cWriteBlockData(int handle, int i2cReg, const char *txBuf, int count); + +%rename(_i2c_read_block_data) lgI2cReadBlockData; +extern int lgI2cReadBlockData(int handle, int i2cReg, char *rx32Buf); + +%rename(_i2c_block_process_call) lgI2cBlockProcessCall; +extern int lgI2cBlockProcessCall(int handle, int i2cReg, char *ioBuf, int count); + +%rename(_i2c_read_i2c_block_data) lgI2cReadI2CBlockData; +extern int lgI2cReadI2CBlockData(int handle, int i2cReg, char *rxBuf, int count); + +%rename(_i2c_write_i2c_block_data) lgI2cWriteI2CBlockData; +extern int lgI2cWriteI2CBlockData(int handle, int i2cReg, const char *txBuf, int count); + +%rename(_i2c_read_device) lgI2cReadDevice; +extern int lgI2cReadDevice(int handle, char *rxBuf, int count); + +%rename(_i2c_write_device) lgI2cWriteDevice; +extern int lgI2cWriteDevice(int handle, const char *txBuf, int count); + +%rename(_i2c_segments) lgI2cSegments; +extern int lgI2cSegments(int handle, lgI2cMsg_t *segs, int count); + +%rename(_i2c_zip) lgI2cZip; +extern int lgI2cZip(int handle, const char *txBuf, int txCount, char *rxBuf, int rxCount); + +%rename(_serial_open) lgSerialOpen; +extern int lgSerialOpen(const char *serDev, int serBaud, int serFlags); + +%rename(_serial_close) lgSerialClose; +extern int lgSerialClose(int handle); + +%rename(_serial_write_byte) lgSerialWriteByte; +extern int lgSerialWriteByte(int handle, int byteVal); + +%rename(_serial_read_byte) lgSerialReadByte; +extern int lgSerialReadByte(int handle); + +%rename(_serial_write) lgSerialWrite; +extern int lgSerialWrite(int handle, const char *txBuf, int count); + +%rename(_serial_read) lgSerialRead; +extern int lgSerialRead(int handle, char *rxBuf, int count); + +%rename(_serial_data_available) lgSerialDataAvailable; +extern int lgSerialDataAvailable(int handle); + +%rename(_spi_open) lgSpiOpen; +extern int lgSpiOpen(int spiDev, int spiChan, int spiBaud, int spiFlags); + +%rename(_spi_close) lgSpiClose; +extern int lgSpiClose(int handle); + +%rename(_spi_read) lgSpiRead; +extern int lgSpiRead(int handle, char *rxBuf, int count); + +%rename(_spi_write) lgSpiWrite; +extern int lgSpiWrite(int handle, const char *txBuf, int count); + +%rename(_spi_xfer) lgSpiXfer; +extern int lgSpiXfer(int handle, const char *txBuf, char *rxBuf, int count); + +%rename(_get_lg_version) lguVersion; +extern int lguVersion(void); + +%rename(_get_internal) lguGetInternal; +extern int lguGetInternal(int cfgId, uint64_t *OUTPUT); + +%rename(_set_internal) lguSetInternal; +extern int lguSetInternal(int cfgId, uint64_t cfgVal); + +%rename(_error_text) lgErrStr; +extern const char *lgErrStr(int error); + diff --git a/PY_LGPIO/lgpio_extra.py b/PY_LGPIO/lgpio_extra.py new file mode 100644 index 0000000..f567424 --- /dev/null +++ b/PY_LGPIO/lgpio_extra.py @@ -0,0 +1,2249 @@ +import os +import socket +import struct +import sys +import threading +import time + +LGPIO_PY_VERSION = 0x00000000 + +exceptions = True + +# 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 + +# GPIO event flags + +RISING_EDGE = 1 +FALLING_EDGE = 2 +BOTH_EDGES = 3 + +# tx constants + +TX_PWM = 0 +TX_WAVE = 1 + +SPI_MODE_0 = 0 +SPI_MODE_1 = 1 +SPI_MODE_2 = 2 +SPI_MODE_3 = 3 + +# lgpio 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 + +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 + +def _tobuf(x): + if isinstance(x, (bytes, bytearray)): + return x + elif isinstance(x, (str)): + return x.encode('latin-1') + elif isinstance(x, (list, tuple)): + return bytearray(x) + else: + raise error("can't convert to bytes") + +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 lgpio 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 lgpio 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 + +class _callback_ADT: + """ + A class to hold callback information. + """ + + def __init__(self, gpiochip, gpio, edge, func): + """ + Initialises a callback. + + 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 lgpio notification callbacks. + """ + + def __init__(self): + """ + Initialises notifications. + """ + threading.Thread.__init__(self) + self._notify = _lgpio._notify_open() + self._file = open('lgd-nfy{}'.format(self._notify), 'rb') + self.go = False + self.daemon = True + self.callbacks = [] + self.go = True + self.start() + + def stop(self): + """ + Stops notifications. + """ + if self.go: + self.go = False + + 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. + """ + + RECV_SIZ = 16 + MSG_SIZ = 16 # 4 bytes of padding in each message + + buf = bytes() + while self.go: + + buf += self._file.read(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.chip == chip and cb.gpio == gpio: + cb.func(chip, gpio, level, tick) + else: # no flags currently defined, ignore. + pass + + buf = buf[offset:] + + self._file.close() + +_notify_thread = _callback_thread() + +class _callback: + """ + A class to provide GPIO level change callbacks. + """ + + def __init__(self, chip, gpio, edge=RISING_EDGE, func=None): + """ + Initialise a callback and adds it to the notification thread. + """ + self.count=0 + self._reset = False + if func is None: + func=self._tally + self.callb = _callback_ADT(chip, gpio, edge, func) + _notify_thread.append(self.callb) + + def cancel(self): + """ + Cancels a callback by removing it from the notification thread. + """ + _notify_thread.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): + """ + """ + return self.count + + def reset_tally(self): + """ + """ + self._reset = True + self.count = 0 + +def get_module_version(): + """ + Returns the version number of the lgpio 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 change + """ + return "lgpio.py_{}.{}.{}.{}".format( + (LGPIO_PY_VERSION>>24)&0xff, (LGPIO_PY_VERSION>>16)&0xff, + (LGPIO_PY_VERSION>>8)&0xff, LGPIO_PY_VERSION&0xff) + +# GPIO + +def gpiochip_open(gpiochip): + """ + This returns a handle to a gpiochip device. + + 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 + ... + """ + handle = u2i(_lgpio._gpiochip_open(gpiochip)) + if handle >= 0: + handle = handle | (gpiochip << 16) + return _u2i(handle) + +def gpiochip_close(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) + ... + """ + return _u2i(_lgpio._gpiochip_close(handle&0xffff)) + +def gpio_get_chip_info(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. + """ + return _u2i_list(_lgpio._gpio_get_chip_info(handle&0xffff)) + + +def gpio_get_line_info(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, GPIO number, + line flags, name, and user. + + On failure returns a negative error code. + """ + return _u2i_list(_lgpio._gpio_get_line_info(handle&0xffff, gpio)) + + +def gpio_get_mode(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. + + Mode 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: --- + 6 @ 64 @ Kernel: --- + 7 @ 128 @ Kernel: --- + 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: --- + """ + return _u2i(_lgpio._gpio_get_mode(handle&0xffff, gpio)) + + +def gpio_claim_input(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, or open source. + + ... + sbc.gpio_claim_input(h, 23) # open GPIO 23 for input. + ... + """ + return _u2i(_lgpio._gpio_claim_input(handle&0xffff, lFlags, gpio)) + +def gpio_claim_output(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, or open source. + + 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. + ... + """ + return _u2i(_lgpio._gpio_claim_output(handle&0xffff, lFlags, gpio, level)) + +def gpio_free(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. + """ + return _u2i(_lgpio._gpio_free(handle&0xffff, gpio)) + +def group_claim_input(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, or open source. + + 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): + GPIO = bytearray() + for g in gpio: + GPIO.extend(struct.pack("I", g)) + return _u2i(_lgpio._group_claim_input(handle&0xffff, lFlags, GPIO)) + else: + return 0 + +def group_claim_output(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, or open source. + + 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 + + GPIO = bytearray() + for g in gpio: + GPIO.extend(struct.pack("I", g)) + + LEVELS = bytearray() + for l in levels: + LEVELS.extend(struct.pack("I", l)) + + return _u2i(_lgpio._group_claim_output( + handle&0xffff, lFlags, GPIO, LEVELS)) + else: + return 0 + +def group_free(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. + + """ + return _u2i(_lgpio._group_free(handle&0xffff, gpio)) + +def gpio_read(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. + + """ + return _u2i(_lgpio._gpio_read(handle&0xffff, gpio)) + +def gpio_write(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). + + """ + return _u2i(_lgpio._gpio_write(handle&0xffff, gpio, level)) + + +def group_read(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. + + """ + return _u2i_list(_lgpio._group_read(handle&0xffff, gpio)) + +def group_write(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. + + """ + return _u2i(_lgpio._group_write( + handle&0xffff, gpio, group_bits, group_mask)) + + +def tx_pulse(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. + + """ + return _u2i(_lgpio._tx_pulse( + handle&0xffff, gpio, pulse_on, pulse_off, pulse_offset, pulse_cycles)) + + +def tx_pwm(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. + + """ + return _u2i(_lgpio._tx_pwm( + handle&0xffff, gpio, pwm_frequency, pwm_duty_cycle, + pulse_offset, pulse_cycles)) + + +def tx_servo(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. + + """ + return _u2i(_lgpio._tx_servo( + handle&0xffff, gpio, pulse_width, servo_frequency, + pulse_offset, pulse_cycles) +) + + +def tx_wave(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): + PULSES = bytearray() + for p in pulses: + PULSES.extend(struct.pack( + "QQQ", p.group_bits, p.group_mask, p.pulse_delay)) + return _u2i(_lgpio._tx_wave(handle&0xffff, gpio, PULSES) +) + else: + return 0 + + +def tx_busy(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. + + """ + return _u2i(_lgpio._tx_busy(handle&0xffff, gpio, kind)) + +def tx_room(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. + + """ + return _u2i(_lgpio._tx_room(handle&0xffff, gpio, kind)) + +def gpio_set_debounce_micros(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. + + """ + return _u2i(_lgpio._gpio_set_debounce_micros( + handle&0xffff, gpio, debounce_micros)) + + +def gpio_set_watchdog_micros(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. + """ + return _u2i(_lgpio._gpio_set_watchdog_micros( + handle&0xffff, gpio, watchdog_micros)) + + +def gpio_claim_alert( + 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 event flags are used to generate alerts for a rising edge, + falling edge, or both edges. + + The line flags may be used to set the GPIO + as active low, open drain, or open source. + + 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 = _notify_thread._notify + return _u2i(_lgpio._gpio_claim_alert( + handle&0xffff, lFlags, eFlags, gpio, notify_handle)) + +def callback(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) + + The timestamp is when the change happened reported as the + number of nanoseconds since the epoch (start of 1970). + + 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, lgpio.BOTH_EDGES, cbf) + + cb2 = sbc.callback(0, 4, lgpio.BOTH_EDGES) + + cb3 = sbc.callback(0, 17) + + print(cb3.tally()) + + cb3.reset_tally() + + cb1.cancel() # To cancel callback cb1. + ... + """ + return _callback(handle>>16, gpio, edge, func) + + +# I2C + +def i2c_open(i2c_bus, i2c_address, i2c_flags=0): + """ + Returns a handle (>= 0) for the device at the I2C bus address. + + 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 + ... + """ + return _u2i(_lgpio._i2c_open(i2c_bus, i2c_address, i2c_flags)) + +def i2c_close(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) + ... + """ + return _u2i(_lgpio._i2c_close(handle)) + +def i2c_write_quick(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 + ... + """ + return _u2i(_lgpio._i2c_write_quick(handle, bit)) + +def i2c_write_byte(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 + ... + """ + return _u2i(_lgpio._i2c_write_byte(handle, byte_val)) + +def i2c_read_byte(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 + ... + """ + return _u2i(_lgpio._i2c_read_byte(handle)) + +def i2c_write_byte_data(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 + . . + + ... + sbc.i2c_write_byte_data(1, 2, 0xC5) + + sbc.i2c_write_byte_data(2, 4, 9) + ... + """ + return _u2i(_lgpio._i2c_write_byte_data(handle, reg, byte_val)) + +def i2c_write_word_data(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 + . . + + ... + sbc.i2c_write_word_data(4, 5, 0xA0C5) + + sbc.i2c_write_word_data(5, 2, 23) + ... + """ + return _u2i(_lgpio._i2c_WriteWordData(handle, reg, word_val)) + +def i2c_read_byte_data(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 + . . + + ... + b = sbc.i2c_read_byte_data(2, 17) + + b = sbc.i2c_read_byte_data(0, 1) + ... + """ + return _u2i(_lgpio._i2c_read_byte_data(handle, reg)) + +def i2c_read_word_data(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 + . . + + ... + w = sbc.i2c_read_word_data(3, 2) + + w = sbc.i2c_read_word_data(2, 7) + ... + """ + return _u2i(_lgpio._i2c_read_word_data(handle, reg)) + +def i2c_process_call(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) + ... + """ + return _u2i(_lgpio._i2c_process_call(handle, reg, word_val)) + +def i2c_write_block_data(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]) + ... + """ + return _u2i(_lgpio._i2c_write_block_data(handle, reg, _tobuf(data))) + +def i2c_read_block_data(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. + + The amount of returned data is set by the device. + + 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 + . . + + ... + (b, d) = sbc.i2c_read_block_data(h, 10) + if b >= 0: + process data + else: + process read failure + ... + """ + return _u2i_list(_lgpio._i2c_read_block_data(handle, reg)) + +def i2c_block_process_call(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]) + ... + """ + return _u2i_list(_lgpio._i2c_block_process_call(handle, reg, _tobuf(data))) + +def i2c_write_i2c_block_data(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]) + ... + """ + return _u2i(_lgpio._i2c_write_i2c_block_data( + handle, reg, _tobuf(data))) + +def i2c_read_i2c_block_data(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 + ... + """ + return _u2i_list(_lgpio._i2c_read_i2c_block_data(handle, reg, count)) + +def i2c_read_device(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) + ... + """ + return _u2i_list(_lgpio._i2c_read_device(handle, count)) + +def i2c_write_device(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]) + ... + """ + return _u2i(_lgpio._i2c_write_device(handle, _tobuf(data))) + + +def i2c_zip(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 + ... + """ + return _u2i_list(_lgpio._i2c_zip(handle, _tobuf(data))) + +# NOTIFICATIONS + +def notify_open(): + """ + Opens a notification pipe. + + 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 the epoch (start of 1970). + 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(_lgpio._notify_open()) + +def notify_pause(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) + ... + ... + """ + return _u2i(_lgpio._notify_pause(handle)) + +def notify_resume(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) + ... + """ + return _u2i(_lgpio._notify_resume(handle)) + +def notify_close(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) + ... + ... + """ + return _u2i(_lgpio._notify_close(handle)) + +# SERIAL + +def serial_open(tty, baud, ser_flags=0): + """ + Returns a handle for the serial tty device opened + at baud bits per second. The device muse be in /dev. + + 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("ttyAMA0", 300) + + h2 = sbc.serial_open("ttyUSB1", 19200, 0) + + h3 = sbc.serial_open("serial0", 9600) + ... + """ + return _u2i(_lgpio._serial_open(tty, baud, ser_flags)) + +def serial_close(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) + ... + """ + return _u2i(_lgpio._serial_close(handle)) + +def serial_read_byte(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) + ... + """ + return _u2i(_lgpio._serial_read_byte(handle)) + +def serial_write_byte(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')) + ... + """ + return _u2i(_lgpio._serial_write_byte(handle, byte_val)) + +def serial_read(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 + ... + """ + return _u2i_list(_lgpio._serial_read(handle, count)) + +def serial_write(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]) + ... + """ + return _u2i(_lgpio._serial_write(handle, _tobuf(data))) + +def serial_data_available(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) + ... + """ + return _u2i(_lgpio._serial_data_available(handle)) + + +# SPI + +def spi_open(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. + + 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. + + ... + h = sbc.spi_open(1, 50000, 3) + ... + """ + return _u2i(_lgpio._spi_open(spi_device, spi_channel, baud, spi_flags)) + +def spi_close(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) + ... + """ + return _u2i(_lgpio._spi_close(handle)) + +def spi_read(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 + ... + """ + return _u2i_list(_lgpio._spi_read(handle, count)) + +def spi_write(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 + ... + """ + return _u2i(_lgpio._spi_write(handle, _tobuf(data))) + +def spi_xfer(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") + ... + """ + return _u2i_list(_lgpio._spi_xfer(handle, _tobuf(data))) + + +# UTILITIES + +def get_internal(config_id): + """ + Returns the value of a configuration item. + + 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) + ... + """ + return _u2i_list(_lgpio._get_internal(config_id)) + +def set_internal(config_id, config_value): + """ + Sets the value of a configuration item. + + 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) + ... + """ + return _u2i(_lgpio._set_internal(config_id, config_value)) + +def error_text(errnum): + """ + Returns a description of an error number. + + errnum:= <0, the error number + + ... + print(sbc.error_text(-5)) + level not 0-1 + ... + """ + return _lgpio._error_text(errnum) + +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. + + 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 [*lgpio.error_text*] for the error text. + + 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 + + [*gpiochip_open*] + [*i2c_open*] + [*notify_open*] + [*serial_open*] + [*spi_open*] + + 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 + . . + + notify_handle: + This associates a notification with a GPIO alert. + + 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. + + ser_flags: 32 bit + No serial flags are currently defined. + + servo_frequency:: 40-500 Hz + Servo pulse frequency + + 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. ttyAMA0, ttyUSB0 + + uint32: + An unsigned 32 bit number. + + watchdog_micros: + The watchdog time in microseconds. + + word_val: 0-65535 + A whole number. + """ + pass + diff --git a/PY_LGPIO/setup.py b/PY_LGPIO/setup.py new file mode 100644 index 0000000..33e8557 --- /dev/null +++ b/PY_LGPIO/setup.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python + +""" +setup.py file for SWIG lgpio +""" + +from distutils.core import setup, Extension + +lgpio_module = Extension('_lgpio', sources=['lgpio_wrap.c',], libraries=['lgpio',],) + +setup (name = 'lgpio', + version = '0.0.0.0', + ext_modules = [lgpio_module], + py_modules = ["lgpio"], + ) + diff --git a/PY_RGPIO/rgpio.py b/PY_RGPIO/rgpio.py new file mode 100644 index 0000000..fdfe796 --- /dev/null +++ b/PY_RGPIO/rgpio.py @@ -0,0 +1,3761 @@ +""" +[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 = 0x00000000 + +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 + +# 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 lgd -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 ADDR environment variable. + port:= the port number on which the rgpiod daemon is listening. + The default is 8889 unless overridden by the 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. + + ... + 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) + + 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.rstrip('\0'), label.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. + + 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.rstrip('\0'), user.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. + + Mode 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: --- + 6 @ 64 @ Kernel: --- + 7 @ 128 @ Kernel: --- + 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: --- + """ + 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, or open source. + + ... + 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, or open source. + + 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, or open source. + + 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, or open source. + + 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, or open source. + + 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) + + The timestamp is when the change happened reported as the + number of nanoseconds since the epoch (start of 1970). + + 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 the epoch (start of 1970). + 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. The device muse be in /dev. + + 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("ttyAMA0", 300) + + h2 = sbc.serial_open("ttyUSB1", 19200, 0) + + h3 = sbc.serial_open("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 + . . + + 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. ttyAMA0, 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/PY_RGPIO/setup.py b/PY_RGPIO/setup.py new file mode 100644 index 0000000..f36434a --- /dev/null +++ b/PY_RGPIO/setup.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python + +from distutils.core import setup + +setup(name='rgpio', + version='0.0.0.0', + author='joan', + author_email='joan@abyz.me.uk', + maintainer='joan', + maintainer_email='joan@abyz.me.uk', + url='http://abyz.me.uk/rpi/lg/python.html', + description='Linux SBC GPIO module', + long_description='Linux SBC Python module to access the lg daemon', + download_url='http://abyz.me.uk/lg/lg.zip', + license='unlicense.org', + py_modules=['rgpio'], + keywords=['linux', 'sbc', 'gpio',], + classifiers=[ + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 3", + ] + ) + diff --git a/README b/README new file mode 100644 index 0000000..8dd6d6b --- /dev/null +++ b/README @@ -0,0 +1,94 @@ +NOTE + +The overall install takes just over 1 minute. + +INSTALL + +Extract the archive to a directory. + +IN THAT DIRECTORY + +Enter the following commands (in this order) + +make +sudo make c +sudo make python + +This will install + +o the library (liblgpio.so) in /usr/local/lib +o the library (librgpio.so) in /usr/local/lib +o the header file (lgpio.h) in /usr/local/include +o the header file (rgpio.h) in /usr/local/include +o the daemon (rgpiod) in /usr/local/bin +o the socket interface (rgs) in /usr/local/bin +o man pages in /usr/local/man/man1 and /usr/local/man/man3 +o the lgpio and rgpio Python modules + +EXAMPLE CODE + +See the EXAMPLES directory + +o lgpio - C (local) +o rgpio - C (local&remote) +o py_lgpio - Python (local) +o py_rgpio - Python (local&remote) +o rgs - shell + +HTML + +To build the HTML site use + +make html + +DAEMON + +To launch the daemon do + +rgpiod & + +Use rgpiod -? to see options. + +PYTHON MODULE + +The Python modules are installed to the default Python location +for Python 2 and Python 3. + +You can install them for additional Python versions as follows. + +Go to the PY_LGPIO and PY_RGPIO directories and enter the +command. + +pythonx.y setup.py install + +where x.y is the Python version. + +STOP DAEMON + +To stop the rgpiod daemon + +killall rgpiod + +RUNNING ON NON LINUX SBC + +On Windows machines (and possibly Macs) + +The Python rgpio module should install with + +python setup.py install + +DOCUMENTATION + +The most up to date should be http://abyz.me.uk/lg/ + +On the SBC try + +man rgs +man rgpiod + +man lgpio +man rgpio + +pydoc lgpio +pydoc rgpio + diff --git a/README.md b/README.md new file mode 100644 index 0000000..4a83f63 --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ + +lgpio is a library for Linux Single Board Computers (SBC) which allows control of the General Purpose Input Outputs (GPIO). + +## Features + +* reading and writing GPIO singly and in groups +* software timed PWM and waves +* callbacks on GPIO level change +* notifications via pipe on GPIO level change +* I2C wrapper +* SPI wrapper +* serial link wrapper +* daemon interface +* permission control (daemon interface) +* file handling (daemon interface) +* creating and running scripts (daemon interface) +* network access (daemon interface) + +## Interfaces + +The library provides a number of control interfaces +* the C function interface, +* the socket interface (used by the Python module). + +## Utilities + +A number of utility programs are provided: +* the rgpiod daemon, +* the Python modules, +* the rgs command line utility, + +## Documentation + +See http://abyz.me.uk/lg/ + +## Example programs + +See http://abyz.me.uk/lg/examples.html + +## GPIO + +ALL GPIO are identified by their gpiochip line number. + diff --git a/UNLICENCE b/UNLICENCE new file mode 100644 index 0000000..471f09f --- /dev/null +++ b/UNLICENCE @@ -0,0 +1,25 @@ +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 + diff --git a/lgCfg.c b/lgCfg.c new file mode 100644 index 0000000..86885cf --- /dev/null +++ b/lgCfg.c @@ -0,0 +1,456 @@ +/* +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 +*/ + +#include +#include +#include +#include +#include + +#include "lgCfg.h" + +#define CFG_COMMENT '#' +#define CFG_KEYVAL_SEP '=' +#define CFG_SSECT '[' +#define CFG_ESECT ']' + +#define CFG_MAGIC 3705902252 + +typedef struct lgCfgKV_s lgCfgKV_t, *lgCfgKV_p; + +typedef struct lgCfgKV_s +{ + char *name; + char *value; + lgCfgKV_p next_key; +} lgCfgKV_t, *lgCfgKV_p; + +typedef struct lgCfgS_s lgCfgS_t, *lgCfgS_p; + +typedef struct lgCfgS_s +{ + char *name; + lgCfgS_p next_section; + lgCfgKV_p first_key; +} lgCfgS_t, *lgCfgS_p; + +typedef struct lgCfg_s +{ + uint32_t magic; + char *file; + lgCfgS_p first_section; +} lgCfg_t, *lgCfg_p; + +lgCfg_p lgCfgNew(char *file) +{ + lgCfg_p cfg; + + cfg = calloc(1, sizeof(lgCfg_t)); + + if (cfg == NULL) return NULL; + + cfg->file = strdup(file); + + if (cfg->file == NULL) + { + free(cfg); + return NULL; + } + + cfg->first_section = calloc(1, sizeof(lgCfgS_t)); + + if (cfg->first_section == NULL) + { + free(cfg->file); + free(cfg); + return NULL; + } + + cfg->first_section->name = strdup("global"); + + if (cfg->first_section->name == NULL) + { + free(cfg->first_section); + free(cfg->file); + free(cfg); + return NULL; + } + + cfg->first_section->next_section = NULL; + + cfg->first_section->first_key = NULL; + + cfg->magic = CFG_MAGIC; + + return cfg; +} + +lgCfgKV_p lgCfgFindKey(lgCfgS_p section, char *key) +{ + lgCfgKV_p k; + + for (k = section->first_key; k; k = k->next_key) + { + if (!strcmp(k->name, key)) break; + } + return k; +} + +lgCfgS_p lgCfgFindSection(lgCfg_p cfg, char *section) +{ + lgCfgS_p s; + + for (s = cfg->first_section; s; s = s->next_section) + { + if (!strcmp(s->name, section)) break; + } + return s; +} + +char *lgCfgGetValue(lgCfg_p cfg, char *section, char *key) +{ + lgCfgS_p cfgS; + lgCfgKV_p cfgKV; + + cfgS = lgCfgFindSection(cfg, section); + if (cfgS != NULL) + { + cfgKV = lgCfgFindKey(cfgS, key); + if (cfgKV != NULL) return cfgKV->value; + } + return NULL; + } + +static lgCfgS_p lgCfgAddSection(lgCfg_p cfg, char *section) +{ + lgCfgS_p s; + + s = lgCfgFindSection(cfg, section); + + if (s == NULL) + { + s = calloc(1, sizeof(lgCfgS_t)); + + if (s == NULL) return NULL; + + s->name = strdup(section); + + if (s->name == NULL) {free(s); return NULL;} + + s->first_key = NULL; + + s->next_section = cfg->first_section; + + cfg->first_section = s; + } + + return s; +} + +lgCfgKV_p lgCfgAddKeyValue(lgCfg_p cfg, lgCfgS_p section, char *key, char *value) +{ + lgCfgKV_p kv; + + kv = calloc(1, sizeof(lgCfgKV_t)); + + if (kv == NULL) return NULL; + + kv->name = strdup(key); + + if (kv->name == NULL) {free(kv); return NULL;} + + kv->value = strdup(value); + + if (kv->value == NULL) {free(kv->name); free(kv); return NULL;} + + kv->next_key = section->first_key; + + section->first_key = kv; + + return kv; +} + +static int lgCfgReadSection(lgCfg_p cfg, char *p, char **section) +{ + char *q, *r; + + *section = NULL; + + ++p; + + while (*p && isspace(*p)) ++p; + + for (q = p; + *q && (*q != '\r') && (*q != '\n') && (*q != CFG_ESECT); + ++q) ; + + if (*q != CFG_ESECT) + { + return CFG_BAD_FILE; + } + + r = q + 1; + + while (*q && (q > p) && isspace(*(q - 1))) --q; + + if (q == p) return CFG_BAD_FILE; + + *q = 0; + *section = p; + + while (*r && isspace(*r)) ++r; + + if (*r && (*r != CFG_COMMENT)) return CFG_BAD_FILE; + + return CFG_OKAY; +} + +static int lgCfgReadKeyValue(lgCfg_p cfg, char *p, char **key, char **val) +{ + char *q, *v; + + *key = NULL; + *val = NULL; + + while (*p && isspace(*p)) ++p; + + for (q = p; + *q && (*q != '\r') && (*q != '\n') && (*q != CFG_KEYVAL_SEP); + ++q) ; + + if (*q != CFG_KEYVAL_SEP) return CFG_BAD_FILE; + + v = q + 1; + + while (*q && (q > p) && isspace(*(q - 1))) --q; + + if (q == p) /* no key name */ return CFG_BAD_FILE; + + *q = 0; + + *key = p; + + while (*v && isspace(*v)) ++v; + + for (q = v; + *q && (*q != '\r') && (*q != '\n') && (*q != CFG_COMMENT); + ++q) ; + + while (*q && (q > v) && isspace(*(q - 1))) --q; + + if (q == v) /* no value */ return CFG_MISSING_VALUE; + + *q = 0; + *val = v; + + return CFG_OKAY; +} + +void lgCfgFree(lgCfg_p cfg) +{ + lgCfgS_p s, s_next; + lgCfgKV_p kv, kv_next; + + if ((cfg == NULL) || (cfg->magic != CFG_MAGIC)) return; + + s = cfg->first_section; + + while (s) + { + s_next = s->next_section; + + kv = s->first_key; + + while (kv) + { + kv_next = kv->next_key; + free(kv->name); + free(kv->value); + free(kv); + kv = kv_next; + } + + free(s->name); + free(s); + s = s_next; + } + + free(cfg->file); + free(cfg); +} + +lgCfg_p lgCfgRead(char *file) +{ + FILE *fp; + lgCfgS_p cs = NULL; /* current section */ + char *p = NULL; + char *section = ""; + char *key = NULL; + char *val = NULL; + + lgCfg_p cfg = NULL; + int err = CFG_OKAY; + char buf[2048]; + int bufPos; + int done; + + fp = fopen(file, "r"); + + if (fp == NULL) + { + return NULL; + } + + cfg = lgCfgNew(file); + + if (cfg == NULL) + { + fclose(fp); + return NULL; + } + + cs = cfg->first_section; + + while (!feof(fp)) + { + bufPos = 0; + + done = 0; + + while (!done) + { + if (fgets(buf+bufPos, sizeof(buf)-bufPos, fp) != NULL) + { + bufPos = strlen(buf); + + if ((bufPos < 2) || + (buf[bufPos-1] != '\n') || + (buf[bufPos-2] != '\\') || + (bufPos >= (sizeof(buf)-2))) + done = 1; + else bufPos -= 2; + } + else done = 1; + } + + if (!bufPos) continue; + + for (p = buf; *p && isspace(*p); ++p) ; + + if (!*p || (*p == CFG_COMMENT)) continue; + + if (*p == CFG_SSECT) + { + err = lgCfgReadSection(cfg, p, §ion); + if (err != CFG_OKAY) + { + lgCfgFree(cfg); + cfg = NULL; + break; + } + + cs = lgCfgAddSection(cfg, section); + if (cs == NULL) + { + lgCfgFree(cfg); + cfg = NULL; + break; + } + } + else + { + err = lgCfgReadKeyValue(cfg, p, &key, &val); + if (err != CFG_OKAY) + { + lgCfgFree(cfg); + cfg = NULL; + break; + } + + if (lgCfgAddKeyValue(cfg, cs, key, val) == NULL) + { + lgCfgFree(cfg); + cfg = NULL; + break; + } + } + } + + fclose(fp); + + return cfg; +} + +void lgCfgPrint(lgCfg_p cfg, FILE *stream) +{ + lgCfgS_p s; + lgCfgKV_p kv; + + if ((cfg == NULL) || (cfg->magic != CFG_MAGIC)) return; + + for (s = cfg->first_section; s; s = s->next_section) + { + if (s->name) fprintf(stream, "[%s]\n", s->name); + + for (kv = s->first_key; kv; kv = kv->next_key) + + fprintf(stream, "%s=%s\n", kv->name, kv->value); + + fprintf(stream, "\n"); + } +} + +char *lgCfgStrip(char *str) +{ + char *end; + + while(isspace((unsigned char)*str)) str++; + + if(*str == 0) return str; + + end = str + strlen(str) - 1; + while (end > str && isspace((unsigned char)*end)) end--; + + end[1] = '\0'; + + return str; +} + +char *lgCfgNextToken(char **str, const char *delim, char **pos) +{ + char *token; + + if (*str) *pos = NULL; + + token = strtok_r(*str, delim, pos); + *str = NULL; + + if (token) return lgCfgStrip(token); + + return NULL; +} + diff --git a/lgCfg.h b/lgCfg.h new file mode 100644 index 0000000..5b9e384 --- /dev/null +++ b/lgCfg.h @@ -0,0 +1,54 @@ +/* +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 +*/ + +#ifndef LG_CFG_H +#define LG_CFG_H + +#include + +#define CFG_OKAY 0 +#define CFG_BAD_FILE -1 +#define CFG_MISSING_VALUE -2 + +typedef struct lgCfg_s lgCfg_t, *lgCfg_p; + +lgCfg_p lgCfgRead(char *file); + +char *lgCfgGetValue(lgCfg_p cfg, char *section, char *key); + +void lgCfgFree(lgCfg_p cfg); + +void lgCfgPrint (lgCfg_p cfg, FILE *stream); + +/* this function alters str, make a copy if you need it again */ +char *lgCfgStrip(char *str); + +/* this function alters str, make a copy if you need it again */ +char *lgCfgNextToken(char **str, const char *delim, char **pos); + +#endif + diff --git a/lgCmd.c b/lgCmd.c new file mode 100644 index 0000000..ffadb1a --- /dev/null +++ b/lgCmd.c @@ -0,0 +1,1086 @@ +/* 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 +*/ + +#include +#include +#include +#include +#include +#include + +#include "lgpio.h" +#include "rgpiod.h" + +#include "lgCmd.h" + +cmdInfo_t cmdInfo[]= +{ + /* num str vfyt retv script*/ + + /* FILES */ + + {LG_CMD_FO, "FO", 102, 2, 0}, // lgFileOpen + {LG_CMD_FC, "FC", 101, 0, 1}, // lgFileClose + {LG_CMD_FR, "FR", 101, 6, 0}, // lgFileRead + {LG_CMD_FW, "FW", 103, 0, 0}, // lgFileWrite + {LG_CMD_FS, "FS", 101, 2, 1}, // lgFileSeek + {LG_CMD_FL, "FL", 102, 6, 0}, // lgFileList + + /* GPIO */ + + {LG_CMD_GO, "GO", 101, 2, 1}, // lgGpiochipOpen + {LG_CMD_GC, "GC", 101, 0, 1}, // lgGpiochipClose + + {LG_CMD_GIC, "GIC", 101, 10, 0}, // lgGpioGetChipInfo + {LG_CMD_GIL, "GIL", 101, 11, 0}, // lgGpioGetLineInfo + {LG_CMD_GMODE, "GMODE", 101, 2, 1}, // lgGpioGetMode + + {LG_CMD_GSI, "GSI", 101, 0, 1}, // lgGpioClaimInput (simple) + {LG_CMD_GSIX, "GSIX", 101, 0, 1}, // lgGpioClaimInput + {LG_CMD_GSO, "GSO", 101, 0, 1}, // lgGpioClaimOutput (simple) + {LG_CMD_GSOX, "GSOX", 101, 0, 1}, // lgGpioClaimOutput + {LG_CMD_GSA, "GSA", 101, 0, 1}, // lgGpioClaimAlert (simple) + {LG_CMD_GSAX, "GSAX", 101, 0, 1}, // lgGpioClaimAlert + {LG_CMD_GSF, "GSF", 101, 0, 1}, // lgGpioFree + + {LG_CMD_GSGI, "GSGI", 101, 0, 0}, // lgGroupClaimInput (simple) + {LG_CMD_GSGIX, "GSGIX", 101, 0, 0}, // lgGroupClaimInput + {LG_CMD_GSGO, "GSGO", 101, 0, 0}, // lgGroupClaimOutput (simple) + {LG_CMD_GSGOX, "GSGOX", 101, 0, 0}, // lgGroupClaimOutput + {LG_CMD_GSGF, "GSGF", 101, 0, 1}, // lgGroupFree + + {LG_CMD_GR, "GR", 101, 2, 1}, // lgGpioRead + {LG_CMD_GW, "GW", 101, 0, 1}, // lgGpioWrite + + {LG_CMD_GGR, "GGR", 101, 9, 1}, // lgGroupRead + {LG_CMD_GGW, "GGW", 101, 0, 1}, // lgGroupWrite (simple) + {LG_CMD_GGWX, "GGWX", 101, 0, 1}, // lgGroupWrite + + {LG_CMD_GP, "GP", 101, 2, 1}, // lgTxPulse (simple) + {LG_CMD_GPX, "GPX", 101, 2, 1}, // lgTxPulse + {LG_CMD_GWAVE, "GWAVE", 101, 2, 1}, // lgTxWave + {LG_CMD_GBUSY, "GBUSY", 101, 2, 1}, // lgTxBusy + {LG_CMD_GROOM, "GROOM", 101, 2, 1}, // lgTxRoom + {LG_CMD_P, "P", 101, 2, 1}, // lgTxPwm (simple) + {LG_CMD_PX, "PX", 101, 2, 1}, // lgTxPwm + {LG_CMD_S, "S", 101, 2, 1}, // lgTxServo (simple) + {LG_CMD_SX, "SX", 101, 2, 1}, // lgTxServo + + {LG_CMD_GDEB, "GDEB", 101, 0, 1}, // lgGpioSetDebounce + {LG_CMD_GWDOG, "GWDOG", 101, 0, 1}, // lgGpioSetWatchdog + + /* I2C */ + + {LG_CMD_I2CO, "I2CO", 101, 2, 1}, // lgI2cOpen + {LG_CMD_I2CC, "I2CC", 101, 0, 1}, // lgI2cClose + + {LG_CMD_I2CWQ, "I2CWQ", 101, 0, 1}, // lgI2cWriteQuick + + {LG_CMD_I2CRS, "I2CRS", 101, 2, 1}, // lgI2cReadByte + {LG_CMD_I2CWS, "I2CWS", 101, 0, 1}, // lgI2cWriteByte + + {LG_CMD_I2CRB, "I2CRB", 101, 2, 1}, // lgI2cReadByteData + {LG_CMD_I2CWB, "I2CWB", 101, 0, 1}, // lgI2cWriteByteData + + {LG_CMD_I2CRW, "I2CRW", 101, 2, 1}, // lgI2cReadWordData + {LG_CMD_I2CWW, "I2CWW", 101, 0, 1}, // lgI2cWriteWordData + + {LG_CMD_I2CRK, "I2CRK", 101, 6, 0}, // lgI2cReadBlockData + {LG_CMD_I2CWK, "I2CWK", 104, 0, 0}, // lgI2cWriteBlockData + + {LG_CMD_I2CRI, "I2CRI", 101, 6, 0}, // lgI2cReadI2CBlockData + {LG_CMD_I2CWI, "I2CWI", 104, 0, 0}, // lgI2cWriteI2CBlockData + + {LG_CMD_I2CRD, "I2CRD", 101, 6, 0}, // lgI2cReadDevice + {LG_CMD_I2CWD, "I2CWD", 103, 0, 0}, // lgI2cWriteDevice + + {LG_CMD_I2CPC, "I2CPC", 101, 2, 1}, // lgI2cProcessCall + {LG_CMD_I2CPK, "I2CPK", 104, 6, 0}, // lgI2cBlockProcessCall + + {LG_CMD_I2CZ, "I2CZ", 103, 6, 0}, // lgI2cZip + + /* NOTIFICATIONS */ + + {LG_CMD_NO, "NO", 100, 2, 1}, // lgNotifyOpen + {LG_CMD_NC, "NC", 101, 0, 1}, // lgNotifyClose + {LG_CMD_NP, "NP", 101, 0, 1}, // lgNotifyPause + {LG_CMD_NR, "NR", 101, 0, 1}, // lgNotifyResume + + /* SCRIPTS */ + + {LG_CMD_PROC, "PROC", 106, 2, 0}, // lgScriptStore + {LG_CMD_PROCR, "PROCR", 107, 0, 0}, // lgScriptRun + {LG_CMD_PROCU, "PROCU", 107, 0, 0}, // lgScriptUpdate + {LG_CMD_PROCP, "PROCP", 101, 7, 0}, // lgScriptStatus + {LG_CMD_PROCS, "PROCS", 101, 0, 0}, // lgScriptStop + {LG_CMD_PROCD, "PROCD", 101, 0, 0}, // lgScriptDelete + + {LG_CMD_PARSE, "PARSE", 106, 0, 0}, // cmdParseScript + + /* SERIAL */ + + {LG_CMD_SERO, "SERO", 108, 2, 0}, // lgSerialOpen + {LG_CMD_SERC, "SERC", 101, 0, 1}, // lgSerialClose + + {LG_CMD_SERRB, "SERRB", 101, 2, 1}, // lgSerialReadByte + {LG_CMD_SERWB, "SERWB", 101, 0, 1}, // lgSerialWriteByte + + {LG_CMD_SERR, "SERR", 101, 6, 0}, // lgSerialRead + {LG_CMD_SERW, "SERW", 103, 0, 0}, // lgSerialWrite + + {LG_CMD_SERDA, "SERDA", 101, 2, 1}, // lgSerialDataAvailable + + /* SHELL */ + + {LG_CMD_SHELL, "SHELL", 109, 2, 0}, // lgShell + + /* SPI */ + + {LG_CMD_SPIO, "SPIO", 101, 2, 0}, // lgSpiOpen + {LG_CMD_SPIC, "SPIC", 101, 0, 1}, // lgSpiClose + + {LG_CMD_SPIR, "SPIR", 101, 6, 0}, // lgSpiRead + {LG_CMD_SPIW, "SPIW", 103, 0, 0}, // lgSpiWrite + + {LG_CMD_SPIX, "SPIX", 103, 6, 0}, // lgSpiXfer + + /* UTILITIES */ + + {LG_CMD_LGV, "LGV", 100, 5, 1}, // lguVersion + {LG_CMD_SBC, "SBC", 100, 6, 1}, // lguSbcName + + {LG_CMD_CGI, "CGI", 101, 3, 1}, // lguGetInternals + {LG_CMD_CSI, "CSI", 101, 1, 1}, // lguSetInternals + + {LG_CMD_TICK, "T", 100, 3, 1}, // lguTimestamp + {LG_CMD_TICK, "TICK", 100, 3, 1}, // lguTimestamp + + {LG_CMD_MICS, "MICS", 101, 0, 1}, // lguSleep + {LG_CMD_MILS, "MILS", 101, 0, 1}, // lguSleep + + {LG_CMD_USER, "U", 105, 6, 0}, // xSetUser + {LG_CMD_USER, "USER", 105, 6, 0}, // xSetUser + + {LG_CMD_SHARE, "C", 101, 0, 0}, // xShareSetUse + {LG_CMD_SHARE, "SHARE", 101, 0, 0}, // xShareSetUse + + {LG_CMD_LCFG, "LCFG", 100, 0, 1}, // xLoadConfig + + {LG_CMD_PCD, "PCD", 100, 6, 1}, // lguGetConfigDir + {LG_CMD_PWD, "PWD", 100, 6, 1}, // lguGetWorkDir + + {LG_CMD_SHRS, "SHRS", 101, 0, 0}, // lgHdlSetShare + {LG_CMD_SHRU, "SHRU", 101, 0, 0}, // xShareUse + + + {LG_CMD_PASSW, "PASSW", 105, 0, 0}, // xPassword + + + // script commands + + {LG_CMD_ADD , "ADD" , 201, 0, 1}, + {LG_CMD_AND , "AND" , 201, 0, 1}, + {LG_CMD_CALL , "CALL" , 202, 0, 1}, + {LG_CMD_CMDR ,"CMDR" , 201, 0, 1}, + {LG_CMD_CMDW , "CMDW" , 201, 0, 1}, + {LG_CMD_CMP , "CMP" , 201, 0, 1}, + {LG_CMD_DCR , "DCR" , 203, 0, 1}, + {LG_CMD_DCRA , "DCRA" , 200, 0, 1}, + {LG_CMD_DIV , "DIV" , 201, 0, 1}, + {LG_CMD_HALT , "HALT" , 200, 0, 1}, + {LG_CMD_INR , "INR" , 203, 0, 1}, + {LG_CMD_INRA , "INRA" , 200, 0, 1}, + {LG_CMD_JGE , "JGE" , 202, 0, 1}, + {LG_CMD_JGT , "JGT" , 202, 0, 1}, + {LG_CMD_JLE , "JLE" , 202, 0, 1}, + {LG_CMD_JLT , "JLT" , 202, 0, 1}, + {LG_CMD_JMP , "JMP" , 202, 0, 1}, + {LG_CMD_JNZ , "JNZ" , 202, 0, 1}, + {LG_CMD_JZ , "JZ" , 202, 0, 1}, + {LG_CMD_LD , "LD" , 204, 0, 1}, + {LG_CMD_LDA , "LDA" , 201, 0, 1}, + {LG_CMD_LDAB , "LDAB" , 201, 0, 1}, + {LG_CMD_MLT , "MLT" , 201, 0, 1}, + {LG_CMD_MOD , "MOD" , 201, 0, 1}, + {LG_CMD_NOP , "NOP" , 200, 0, 1}, + {LG_CMD_OR , "OR" , 201, 0, 1}, + {LG_CMD_POP , "POP" , 203, 0, 1}, + {LG_CMD_POPA , "POPA" , 200, 0, 1}, + {LG_CMD_PUSH , "PUSH" , 203, 0, 1}, + {LG_CMD_PUSHA, "PUSHA", 200, 0, 1}, + {LG_CMD_RET , "RET" , 200, 0, 1}, + {LG_CMD_RL , "RL" , 204, 0, 1}, + {LG_CMD_RLA , "RLA" , 201, 0, 1}, + {LG_CMD_RR , "RR" , 204, 0, 1}, + {LG_CMD_RRA , "RRA" , 201, 0, 1}, + {LG_CMD_SHL , "SHL" , 204, 0, 1}, + {LG_CMD_SHLA , "SHLA" , 201, 0, 1}, + {LG_CMD_SHR , "SHR" , 204, 0, 1}, + {LG_CMD_SHRA , "SHRA" , 201, 0, 1}, + {LG_CMD_STA , "STA" , 203, 0, 1}, + {LG_CMD_STAB , "STAB" , 201, 0, 1}, + {LG_CMD_SUB , "SUB" , 201, 0, 1}, + {LG_CMD_SYS , "SYS" , 206, 0, 1}, + {LG_CMD_TAG , "TAG" , 202, 0, 1}, + {LG_CMD_X , "X" , 205, 0, 1}, + {LG_CMD_XA , "XA" , 203, 0, 1}, + {LG_CMD_XOR , "XOR" , 201, 0, 1}, + +}; + +static int cmdMatch(char *str) +{ + int i; + + for (i=0; i<(sizeof(cmdInfo)/sizeof(cmdInfo_t)); i++) + { + if (strcasecmp(str, cmdInfo[i].name) == 0) return i; + } + return CMD_UNKNOWN_CMD; +} + +static int getNum( + char *str, uintmax_t *val, int8_t *opt, uintmax_t max, int real) +{ + int m, n; + uintmax_t v; + float f; + + *opt = 0; + + if (real) + { + m = sscanf(str, " %f %n", &f, &n); + + if (m == 1) + { + *val = (f * 1000.0) + 0.5; + *opt = CMD_NUMERIC; + if (*val > max) *opt = -CMD_NUMERIC; + return n; + } + } + + m = sscanf(str, " %ji %n", &v, &n); + + if (m == 1) + { + if (real) v = v * 1000; + *val = v; + *opt = CMD_NUMERIC; + if (v > max) *opt = -CMD_NUMERIC; + return n; + } + + m = sscanf(str, " v%ji %n", &v, &n); + + if (m == 1) + { + *val = v; + if (v < LG_MAX_SCRIPT_VARS) *opt = CMD_VAR; + else *opt = -CMD_VAR; + return n; + } + + m = sscanf(str, " p%ji %n", &v, &n); + + if (m == 1) + { + *val = v; + if (v < LG_MAX_SCRIPT_PARAMS) *opt = CMD_PAR; + else *opt = -CMD_PAR; + return n; + } + + return 0; +} + +static char intCmdStr[32]; +static int intCmdIdx; + +char *cmdStr(void) +{ + return intCmdStr; +} + +int cmdScanf(char *text, cmdCtl_p ctlP, lgCmd_p cmdP, char *fmt, int *matched) +{ + uint32_t *argI=(uint32_t*)&cmdP[1]; + uint64_t *argQ=(uint64_t*)&cmdP[1]; + uint8_t *argB=(uint8_t*) &cmdP[1]; + + int par = 0; + char *at = fmt; + uintmax_t var; + int8_t opt; + int i; + int eaten; + int valid = 1; + int upto = 1; + int offset = 0; + + *matched = 0; + + while (valid && *at) + { + switch (*at++) + { + case 'q': + upto = 200; + case 'Q': // 64-bit arg + for (i=0; ieaten += + getNum(text+ctlP->eaten, &var, &opt, 0xffffffffffffffff, 0); + if (opt > 0) + { + argQ[offset/8] = var; + if (paropt[par] = opt; + offset+=8; + ++par; + (*matched)++; + } + else + { + valid = 0; + break; + } + } + upto = 1; + break; + + case 'f': + upto = 200; + case 'F': // 32-bit arg (float) + for (i=0; ieaten += + getNum(text+ctlP->eaten, &var, &opt, 0xffffffff, 1); + if (opt > 0) + { + argI[offset/4] = var; + if (paropt[par] = opt; + offset+=4; + ++par; + (*matched)++; + } + else + { + valid = 0; + break; + } + } + upto = 1; + break; + + case 'i': + upto = 200; + case 'I': // 32-bit arg + for (i=0; ieaten += + getNum(text+ctlP->eaten, &var, &opt, 0xffffffff, 0); + if (opt > 0) + { + argI[offset/4] = var; + if (paropt[par] = opt; + offset+=4; + ++par; + (*matched)++; + } + else + { + valid = 0; + break; + } + } + upto = 1; + break; + + case 'b': + upto = 200; + case 'B': // 8-bit ext + for (i=0; ieaten, &var, &opt, 0xff, 0); + if (opt == CMD_NUMERIC) + { + if (((int)var>=0) && ((int)var<=255)) + { + argB[offset] = var; + ++offset; + ++par; + (*matched)++; + ctlP->eaten += eaten; + } + else + { + valid = 0; + break; + } + } + else + { + valid = 0; + break; + } + } + upto = 1; + break; + + case '*': + upto = 200; + break; + + case '<': + offset -= 4; /* retreat 4 bytes */ + break; + + case '>': + offset += 4; /* advance 4 bytes */ + break; + + default: + valid = 0; + break; + } + } + + return valid; +} + +int cmdParse(char *text, cmdCtl_p ctlP, lgCmd_p cmdP, int cmdBufSize) +{ + int m, valid, idx, pp, pars, n, n2; + int matches; + uint32_t *arg=(uint32_t*)&cmdP[1]; + char *ext=(char*)&cmdP[1]; + uintmax_t var; + uint32_t tI[10]; + int8_t tB[10]; + + bzero(&ctlP->opt, sizeof(ctlP->opt)); + + sscanf(text+ctlP->eaten, " %31s %n", intCmdStr, &pp); + + ctlP->eaten += pp; + + cmdP->cmd = -1; + + idx = cmdMatch(intCmdStr); + + intCmdIdx = idx; + + if (idx < 0) return idx; + + valid = 0; + + cmdP->cmd = cmdInfo[idx].cmd; + cmdP->size = 0; + + switch (cmdInfo[idx].vt) + { + case 100: /* + No parameters, always valid. + */ + pars = 0; + valid = 1; + + break; + + case 101: /* + */ + + switch (cmdP->cmd) + { + case LG_CMD_CGI: // id + case LG_CMD_FC: // h + case LG_CMD_I2CC: // h + case LG_CMD_I2CRS: // h + case LG_CMD_MICS: // v + case LG_CMD_MILS: // v + case LG_CMD_NR: // h + case LG_CMD_NC: // h + case LG_CMD_NP: // h + case LG_CMD_PROCD: // h + case LG_CMD_PROCP: // h + case LG_CMD_PROCS: // h + case LG_CMD_SERC: // h + case LG_CMD_SERDA: // h + case LG_CMD_SERRB: // h + case LG_CMD_SHARE: // v + case LG_CMD_SHRU: // v + case LG_CMD_SPIC: // h + case LG_CMD_GC: // h + case LG_CMD_GIC: // h + case LG_CMD_GO: // gc + pars = 1; + valid = cmdScanf(text, ctlP, cmdP, "I", &matches); + break; + + case LG_CMD_CSI: // id valQ --> valQ id + pars = 3; /* Q counts as 2 */ + valid = cmdScanf(text, ctlP, cmdP, ">>I<< (vQ mQ dQ)* h g + + ctlP->eaten += + getNum(text+ctlP->eaten, &var, &tB[0], 0xffffffff, 0); + tI[0] = var; + + ctlP->eaten += + getNum(text+ctlP->eaten, &var, &tB[1], 0xffffffff, 0); + tI[1] = var; + + cmdScanf(text, ctlP, cmdP, "*Q", &matches); + + if ((tB[0] > 0) && (tB[1] > 0) && + matches && ((matches % 3) == 0)) + { + pars = 2 + (matches * 2); + arg[matches*2] = tI[0]; + arg[(matches*2)+1] = tI[1]; + valid = 1; + } + + break; + + case LG_CMD_FR: // h v + case LG_CMD_GGR: // h g + case LG_CMD_GIL: // h g + case LG_CMD_GMODE: // h g + case LG_CMD_GR: // h g + case LG_CMD_GSI: // h g + case LG_CMD_GSO: // h g + case LG_CMD_GSF: // h g + case LG_CMD_GSGF: // h g + case LG_CMD_I2CRB: // h b + case LG_CMD_I2CRD: // + case LG_CMD_I2CRK: + case LG_CMD_I2CRW: + case LG_CMD_I2CWQ: + case LG_CMD_I2CWS: + case LG_CMD_SERR: + case LG_CMD_SERWB: + case LG_CMD_SHRS: // h v + case LG_CMD_SPIR: + pars = 2; + valid = cmdScanf(text, ctlP, cmdP, "II", &matches); + break; + + case LG_CMD_GDEB: // h g v + case LG_CMD_GROOM: // h g t + case LG_CMD_GBUSY: // h g t + case LG_CMD_GSA: // h g nfyh + case LG_CMD_GSIX: // h lf g + case LG_CMD_GW: // h g v + case LG_CMD_GWDOG: // h g v + case LG_CMD_FS: + case LG_CMD_I2CO: + case LG_CMD_I2CPC: + case LG_CMD_I2CRI: + case LG_CMD_I2CWB: + case LG_CMD_I2CWW: + case LG_CMD_S: // h g width + pars = 3; + valid = cmdScanf(text, ctlP, cmdP, "III", &matches); + break; + + case LG_CMD_P: // h g freq dutyc + pars = 4; + valid = cmdScanf(text, ctlP, cmdP, "IIFF", &matches); + break; + + case LG_CMD_GP: // h g m_on m_off + case LG_CMD_GSOX: // h lf g v + case LG_CMD_SPIO: + pars = 4; + valid = cmdScanf(text, ctlP, cmdP, "IIII", &matches); + break; + + case LG_CMD_GGW: // h g bitsQ -> bitsQ h g + pars = 4; /* Q counts as 2 */ + valid = cmdScanf(text, ctlP, cmdP, ">>II<<< bitsQ maskQ h g + pars = 6; /* Q counts as 2 */ + valid = cmdScanf(text, ctlP, cmdP, ">>>>II<<<<< 1) valid = 1; + break; + + case LG_CMD_GSGIX: // h lf g* + valid = cmdScanf(text, ctlP, cmdP, "i", &matches); + pars = matches; + if (pars > 2) valid = 1; + break; + + case LG_CMD_GSGOX: // h lf g* v* + valid = cmdScanf(text, ctlP, cmdP, "i", &matches); + pars = matches; + if ((pars > 3) && ((pars % 2) == 0)) valid = 1; + break; + } + + if (valid) cmdP->size = pars * 4; + + break; + + case 102: /* + FL FO + Two parameters, first a string, other positive. + */ + m = sscanf(text+ctlP->eaten, " %*s%n %n", &n, &n2); + if ((m >= 0) && n) + { + cmdP->size = n+4; + ctlP->opt[1] = CMD_NUMERIC; + memcpy(ext+4, text+ctlP->eaten, n); + ctlP->eaten += n2; + + ctlP->eaten += + getNum(text+ctlP->eaten, &var, &ctlP->opt[0], 0xffffffff, 0); + arg[0] = var; + + if ((ctlP->opt[0] > 0) && ((int)arg[0] >= 0)) + { + valid = 1; + } + } + + break; + + case 103: /* BI2CZ BSPIX FW I2CWD I2CZ SERW SPIW SPIX + + Two or more parameters, first >=0, rest 0-255. + */ + + valid = cmdScanf(text, ctlP, cmdP, "I>b", &matches); + pars = matches; + + if (pars > 1) + { + valid = 1; + cmdP->size = 4 + (pars-1); + } + + break; + + case 104: /* I2CPK I2CWI I2CWK + + Three to 34 parameters, all 0-255. + */ + + valid = cmdScanf(text, ctlP, cmdP, "II>>b", &matches); + pars = matches; + + if ((pars > 2) && (pars < 35)) + { + valid = 1; + cmdP->size = 8 + (pars-2); + } + + break; + + case 105: /* + PASSW USER + One parameter, a string. + */ + m = sscanf(text+ctlP->eaten, " %*s%n %n", &n, &n2); + if ((m >= 0) && n) + { + cmdP->size = n; + memcpy(ext, text+ctlP->eaten, n); + ctlP->eaten += n2; + valid = 1; + } + + break; + + case 106: /* + PARSE PROC + One parameter, string (rest of input). + */ + cmdP->size = strlen(text+ctlP->eaten); + memcpy(ext, text+ctlP->eaten, cmdP->size); + ctlP->eaten += cmdP->size; + valid = 1; + + break; + + case 107: /* + One to 11 parameters, first positive, + optional remainder, any value. + */ + + valid = cmdScanf(text, ctlP, cmdP, "i", &matches); + pars = matches; + + if ((pars > 0) && (pars <= (LG_MAX_SCRIPT_PARAMS+1))) + { + valid = 1; + cmdP->size = pars * 4; + } + + break; + + case 108: /* + SERO + Three parameters, first a string, rest >=0 + */ + m = sscanf(text+ctlP->eaten, " %*s%n %n", &n, &n2); + if ((m >= 0) && n) + { + cmdP->size = n+8; + ctlP->opt[1] = CMD_NUMERIC; + memcpy(ext+8, text+ctlP->eaten, n); + ctlP->eaten += n2; + + ctlP->eaten += + getNum(text+ctlP->eaten, &var, &ctlP->opt[0], 0xffffffff, 0); + arg[0] = var; + + ctlP->eaten += + getNum(text+ctlP->eaten, &var, &ctlP->opt[1], 0xffffffff, 0); + arg[1] = var; + + if ((ctlP->opt[0] > 0) && ((int)arg[0] >= 0) && + (ctlP->opt[1] > 0) && ((int)arg[1] >= 0)) + valid = 1; + } + + break; + + case 109: /* + SHELL + Two string parameters, the first space teminated. + The second arbitrary. + */ + m = sscanf(text+ctlP->eaten, " %*s%n %n", &n, &n2); + + if ((m >= 0) && n) + { + valid = 1; + + arg[0] = n+1; /* length includes terminating 0 */ + + memcpy(ext+4, text+ctlP->eaten, n); + ext[4+n] = 0; /* terminate first string */ + + ctlP->eaten += n; + + n2 = strlen(text+ctlP->eaten+1); + + memcpy(ext+n+5, text+ctlP->eaten+1, n2); + ext[4+n+n2+1] = 0; /* terminate second string */ + + ctlP->eaten += n2; + ctlP->eaten ++; + cmdP->size = 4+n+n2+2; + } + + break; + + + case 200: /* + Script. No parameters, always valid. + */ + valid = 1; + + break; + + case 201: /* + One parameter, any value. + */ + ctlP->eaten += + getNum(text+ctlP->eaten, &var, &ctlP->opt[0], 0xffffffff, 0); + arg[0] = var; + + if (ctlP->opt[0] > 0) valid = 1; + + break; + + case 202: /* + One numeric parameter, any value. + */ + ctlP->eaten += + getNum(text+ctlP->eaten, &var, &ctlP->opt[0], 0xffffffff, 0); + arg[0] = var; + if (ctlP->opt[0] == CMD_NUMERIC) valid = 1; + + break; + + case 203: /* + One register parameter. + */ + ctlP->eaten += + getNum(text+ctlP->eaten, &var, &ctlP->opt[0], 0xffffffff, 0); + arg[0] = var; + + if ((ctlP->opt[0] > 0) && (arg[0] < LG_MAX_SCRIPT_VARS)) valid = 1; + + break; + + case 204: /* + Two parameters, first register, second any value. + */ + ctlP->eaten += + getNum(text+ctlP->eaten, &var, &ctlP->opt[0], 0xffffffff, 0); + arg[0] = var; + + ctlP->eaten += + getNum(text+ctlP->eaten, &var, &ctlP->opt[1], 0xffffffff, 0); + arg[1] = var; + + if ((ctlP->opt[0] > 0) && + (arg[0] < LG_MAX_SCRIPT_VARS) && + (ctlP->opt[1] > 0)) valid = 1; + + break; + + case 205: /* + Two register parameters. + */ + ctlP->eaten += + getNum(text+ctlP->eaten, &var, &ctlP->opt[0], 0xffffffff, 0); + arg[0] = var; + + ctlP->eaten += + getNum(text+ctlP->eaten, &var, &ctlP->opt[1], 0xffffffff, 0); + arg[1] = var; + + if ((ctlP->opt[0] > 0) && (arg[0] < LG_MAX_SCRIPT_VARS) && + (ctlP->opt[1] > 0) && (arg[1] < LG_MAX_SCRIPT_VARS)) valid = 1; + + break; + + case 206: /* + One parameter, a string. + */ + m = sscanf(text+ctlP->eaten, " %*s%n %n", &n, &n2); + if ((m >= 0) && n) + { + cmdP->size = n; + ctlP->opt[3] = CMD_NUMERIC; + memcpy(ext, text+ctlP->eaten, n); + ctlP->eaten += n2; + valid = 1; + } + + break; + } + + if (valid) return idx; else return CMD_BAD_PARAMETER; +} + +int cmdParseScript(char *script, cmdScript_t *s, int diags) +{ + int idx, len, b, i, j, tags, resolved; + int status; + cmdInstr_t instr; + cmdCtl_t ctlP; + lgCmd_t cmdBuf[CMD_MAX_EXTENSION/sizeof(lgCmd_t)]; + lgCmd_p cmdP=cmdBuf; + uint32_t *arg=(uint32_t*)&cmdP[1]; + + ctlP.inScript = 1; + ctlP.eaten = 0; + + status = 0; + + cmdTagStep_t tag_step[LG_MAX_SCRIPT_TAGS]; + + len = strlen(script); + + /* calloc space for PARAMS, VARS, CMDS, and STRINGS */ + + b = (sizeof(int) * (LG_MAX_SCRIPT_PARAMS + LG_MAX_SCRIPT_VARS)) + + (sizeof(cmdInstr_t) * (len + 2) / 2) + len; + + s->par = calloc(1, b); + + if (s->par == NULL) return -1; + + s->var = s->par + LG_MAX_SCRIPT_PARAMS; + + s->instr = (cmdInstr_t *)(s->var + LG_MAX_SCRIPT_VARS); + + s->str_area = (char *)(s->instr + ((len + 2) / 2)); + + s->str_area_len = len; + s->str_area_pos = 0; + + s->instrs = 0; + + tags = 0; + + idx = 0; + + while (ctlP.eaten= 0) || (idx != CMD_UNKNOWN_CMD)) + { + if (!cmdInfo[intCmdIdx].cvis) idx = CMD_NOT_IN_SCRIPT; + } + + if (idx >= 0) + { + fprintf(stderr, "cmd=%d size=%d a0=%d a1=%d a2=%d a3=%d\n", + cmdP->cmd, cmdP->size, arg[0], arg[1], arg[2], arg[3]); + if (cmdP->size) + { + //memcpy(s->str_area + s->str_area_pos, v, cmdP->size); + //s->str_area[s->str_area_pos + cmdP->size] = 0; + //p[4] = (intptr_t) s->str_area + s->str_area_pos; + //s->str_area_pos += (cmdP->size + 1); + } + + + instr.cmd = cmdP->cmd; + memcpy(&instr.arg[0], &arg[0], sizeof(instr.arg)); + + if (cmdP->cmd == LG_CMD_TAG) + { + if (tags < LG_MAX_SCRIPT_TAGS) + { + /* check tag not already used */ + for (j=0; jinstrs; + tags++; + } + else + { + if (diags) + { + fprintf(stderr, "Too many tags: %"PRId32"\n", instr.arg[0]); + } + if (!status) status = LG_TOO_MANY_TAGS; + idx = -1; + } + } + } + else + { + if (diags) + { + if (idx == CMD_UNKNOWN_CMD) + fprintf(stderr, "Unknown command: %s\n", cmdStr()); + else if (idx == CMD_NOT_IN_SCRIPT) + fprintf(stderr, "Command illegal in script: %s\n", cmdStr()); + else + fprintf(stderr, "Bad parameter to %s\n", cmdStr()); + } + if (!status) status = LG_BAD_SCRIPT_CMD; + } + + if (idx >= 0) + { + if (cmdP->cmd != LG_CMD_TAG) + { + memcpy(instr.opt, &ctlP.opt, sizeof(instr.opt)); + s->instr[s->instrs++] = instr; + } + } + } + + for (i=0; iinstrs; i++) + { + instr = s->instr[i]; + + /* resolve jumps */ + + if ((instr.cmd == LG_CMD_JMP) || (instr.cmd == LG_CMD_CALL) || + (instr.cmd == LG_CMD_JZ) || (instr.cmd == LG_CMD_JNZ) || + (instr.cmd == LG_CMD_JGE) || (instr.cmd == LG_CMD_JGT) || + (instr.cmd == LG_CMD_JLE) || (instr.cmd == LG_CMD_JLT)) + { + resolved = 0; + + for (j=0; jinstr[i].arg[0] = tag_step[j].step; + resolved = 1; + break; + } + } + + if (!resolved) + { + if (diags) + { + fprintf(stderr, "Can't resolve tag %"PRId32"\n", instr.arg[0]); + } + if (!status) status = LG_BAD_TAG; + } + } + } + return status; +} + diff --git a/lgCmd.h b/lgCmd.h new file mode 100644 index 0000000..27ac14f --- /dev/null +++ b/lgCmd.h @@ -0,0 +1,127 @@ +/* +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 +*/ + +#ifndef LG_CMD_H +#define LG_CMD_H + +#include +#include + +#include "lgpio.h" + +#define CMD_MAX_PARAM 512 +#define CMD_MAX_EXTENSION (1<<16) + +#define CMD_UNKNOWN_CMD -1 +#define CMD_BAD_PARAMETER -2 +#define CMD_EXT_TOO_SMALL -3 +#define CMD_NOT_IN_SCRIPT -4 + +#define CMD_MAX_ARG 8 +#define CMD_MAX_OPT 8 + +#define CMD_NUMERIC 1 +#define CMD_VAR 2 +#define CMD_PAR 3 + +#define LG_MAX_SCRIPT_TAGS 50 +#define LG_MAX_SCRIPT_VARS 150 +#define LG_MAX_SCRIPT_PARAMS 10 + +#define LG_MAGIC 0x6c67646d /* ASCII lgdm */ + +typedef struct +{ + union + { + uint32_t magic; + int32_t status; + }; + uint32_t size; + uint16_t cmd; + uint16_t doubles; + uint16_t longs; + uint16_t shorts; +} lgCmd_t, *lgCmd_p; + +typedef struct +{ + int eaten; + int inScript; + int8_t opt[CMD_MAX_OPT]; +} cmdCtl_t, *cmdCtl_p; + +typedef struct +{ + int cmd; /* command number */ + char *name; /* command name */ + int vt; /* command verification type */ + int rv; /* command return value type */ + int cvis; /* command valid in a script */ +} cmdInfo_t; + +typedef struct +{ + uint32_t tag; + int step; +} cmdTagStep_t; + +typedef struct +{ + uint32_t arg[CMD_MAX_ARG]; + uint16_t cmd; + int8_t opt[CMD_MAX_OPT]; +} cmdInstr_t; + +typedef struct +{ + /* + +-----------+---------+---------+----------------+ + | PARAMS... | VARS... | CMDS... | STRING AREA... | + +-----------+---------+---------+----------------+ + */ + int *par; + int *var; + cmdInstr_t *instr; + int instrs; + char *str_area; + int str_area_len; + int str_area_pos; +} cmdScript_t; + +extern cmdInfo_t cmdInfo[]; + +extern char *cmdUsage; + +int cmdParse(char *text, cmdCtl_p ctlP, lgCmd_p cmdP, int cmdBufSize); + +int cmdParseScript(char *script, cmdScript_t *s, int diags); + +char *cmdStr(void); + +#endif + diff --git a/lgCtx.c b/lgCtx.c new file mode 100644 index 0000000..6797f33 --- /dev/null +++ b/lgCtx.c @@ -0,0 +1,72 @@ +/* +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 +*/ + +#include + +#include "lgpio.h" + +#include "lgDbg.h" +#include "lgCtx.h" + +static pthread_key_t slgGlobalKey; + +static pthread_once_t xInited = PTHREAD_ONCE_INIT; + +static void xInit(void) +{ + LG_DBG(LG_DEBUG_ALLOC, ""); + (void) pthread_key_create(&slgGlobalKey, NULL); +} + +lgCtx_p lgCtxGet(void) +{ + lgCtx_p ctx; + + pthread_once(&xInited, xInit); + + LG_DBG(LG_DEBUG_ALLOC, "thread=%llu", (long long int)pthread_self()); + + ctx = pthread_getspecific(slgGlobalKey); + + LG_DBG(LG_DEBUG_ALLOC, "ctx=%p", ctx); + + if (ctx == NULL) + { + ctx = calloc(1, sizeof(lgCtx_t)); + + if (ctx != NULL) + { + // all fields cleared by calloc + pthread_setspecific(slgGlobalKey, ctx); + } + } + + LG_DBG(LG_DEBUG_ALLOC, "ctx=%p", ctx); + + return ctx; +} + diff --git a/lgCtx.h b/lgCtx.h new file mode 100644 index 0000000..f13f4b6 --- /dev/null +++ b/lgCtx.h @@ -0,0 +1,61 @@ +/* +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 +*/ + +#ifndef LG_CONTEXT_H +#define LG_CONTEXT_H + +#include + +typedef struct lgPermit_s +{ + char *files; + char *scripts; + char *i2c; + char *spi; + char *serial; + char *gpio; + char *notify; + char *debug; + char *shell; +} lgPermit_t, *lgPermit_p; + +typedef struct lgCtx_s +{ + char user[LG_USER_LEN]; + char salt1[LG_SALT_LEN]; + char salt2[LG_SALT_LEN]; + int owner; + int approved; + int autoSetShare; + int autoUseShare; + lgPermit_t permits; +} lgCtx_t, *lgCtx_p; + +lgCtx_p lgCtxGet(void); + +#endif + diff --git a/lgDbg.c b/lgDbg.c new file mode 100644 index 0000000..53d17cd --- /dev/null +++ b/lgDbg.c @@ -0,0 +1,124 @@ +/* +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 +*/ + +#include +#include +#include + +#include "lgpio.h" + +#include "lgDbg.h" + +#define LG_DBG_MAX_BUFS 8 + +uint64_t lgDbgLevel = LG_DEBUG_ALWAYS; + +char *lgDbgStr2Hex(int count, const char *buf) +{ + static char str[LG_DBG_MAX_BUFS][128]; + static int which = 0; + + int i, c; + + if (++which >= LG_DBG_MAX_BUFS) which = 0; + + if (count && buf) + { + if (count > 40) c = 40; else c = count; + + for (i=0; i= LG_DBG_MAX_BUFS) which = 0; + + if (count && buf) + { + pos = 0; + + for (i=0; i 100) break; + } + } + else str[which][0] = 0; + + return str[which]; +} + +char *lgDbgTimeStamp(void) +{ + static struct timeval last; + static char buf[32]; + struct timeval now; + + struct tm tmp; + + gettimeofday(&now, NULL); + + if (now.tv_sec != last.tv_sec) + { + localtime_r(&now.tv_sec, &tmp); + strftime(buf, sizeof(buf), "%F %T", &tmp); + last.tv_sec = now.tv_sec; + } + + return buf; +} + +char *lgDbgBuf2Str(int count, const char *buf) +{ + static char str[128]; + int i, c; + + if (count && buf) + { + if (count > 40) c = 40; else c = count; + + for (i=0; i +*/ + +#ifndef LG_DBG_H +#define LG_DBG_H + +#include +#include + +extern uint64_t lgDbgLevel; +extern int lgMinTxDelay; + +/* Debug constants +*/ + +#define LG_DEBUG_MIN_LEVEL (1<<0) +#define LG_DEBUG_ALWAYS (1<<0) +#define LG_DEBUG_TRACE (1<<1) +#define LG_DEBUG_USER (1<<2) +#define LG_DEBUG_SCRIPT (1<<3) +#define LG_DEBUG_STARTUP (1<<4) +#define LG_DEBUG_GPIO (1<<5) +#define LG_DEBUG_ALLOC (1<<6) +#define LG_DEBUG_FILE (1<<7) +#define LG_DEBUG_FAST_TICK (1<<8) +#define LG_DEBUG_FREQUENT (1<<9) +#define LG_DEBUG_INTERNAL (1<<10) +#define LG_DEBUG_INIT (1<<11) +#define LG_DEBUG_MAX_LEVEL (1<<12) + +#define LG_DBG(mask, format, arg...) \ + do \ + { \ + if (lgDbgLevel & mask) \ + fprintf(stderr, "%s %s: " format "\n" , \ + lgDbgTimeStamp(), __FUNCTION__ , ## arg); \ + } \ + while (0) + +#define PARAM_ERROR(x, format, arg...) \ + do \ + { \ + LG_DBG(LG_DEBUG_USER, format, ## arg); \ + return x; \ + } \ + while (0) + +#define ALLOC_ERROR(x, format, arg...) \ + do \ + { \ + LG_DBG(LG_DEBUG_ALWAYS, format, ## arg); \ + return x; \ + } \ + while (0) + +char *lgDbgBuf2Str(int count, const char *buf); +char *lgDbgInt2Str(int count, const int *buf); +char *lgDbgStr2Hex(int count, const char *buf); +char *lgDbgTimeStamp(void); + +#endif + diff --git a/lgErr.c b/lgErr.c new file mode 100644 index 0000000..34534ea --- /dev/null +++ b/lgErr.c @@ -0,0 +1,156 @@ +/* +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 +*/ + +#include "lgpio.h" + +typedef struct +{ + int error; + char *str; +} xErrInfo_t; + +static xErrInfo_t xErrInfo[]= +{ + {LG_OKAY, "No error"}, + {LG_INIT_FAILED, "initialisation failed"}, + {LG_BAD_MICROS, "micros not 0-999999"}, + {LG_BAD_PATHNAME, "can not open pathname"}, + {LG_NO_HANDLE, "no handle available"}, + {LG_BAD_HANDLE, "unknown handle"}, + {LG_BAD_SOCKET_PORT, "socket port not 1024-32000"}, + {LG_NOT_PERMITTED, "GPIO operation not permitted"}, + {LG_SOME_PERMITTED, "one or more GPIO not permitted"}, + {LG_BAD_SCRIPT, "invalid script"}, + {LG_BAD_TX_TYPE, "bad tx type for GPIO and group"}, + {LG_GPIO_IN_USE, "GPIO already in use"}, + {LG_BAD_PARAM_NUM, "script parameter id not 0-9"}, + {LG_DUP_TAG, "script has duplicate tag"}, + {LG_TOO_MANY_TAGS, "script has too many tags"}, + {LG_BAD_SCRIPT_CMD, "illegal script command"}, + {LG_BAD_VAR_NUM, "script variable id not 0-149"}, + {LG_NO_SCRIPT_ROOM, "no more room for scripts"}, + {LG_NO_MEMORY, "can not allocate temporary memory"}, + {LG_SOCK_READ_FAILED, "socket read failed"}, + {LG_SOCK_WRIT_FAILED, "socket write failed"}, + {LG_TOO_MANY_PARAM, "too many script parameters (> 10)"}, + {LG_SCRIPT_NOT_READY, "script initialising"}, + {LG_BAD_TAG, "script has unresolved tag"}, + {LG_BAD_MICS_DELAY, "bad MICS delay (too large)"}, + {LG_BAD_MILS_DELAY, "bad MILS delay (too large)"}, + {LG_I2C_OPEN_FAILED, "can not open I2C device"}, + {LG_SERIAL_OPEN_FAILED, "can not open serial device"}, + {LG_SPI_OPEN_FAILED, "can not open SPI device"}, + {LG_BAD_I2C_BUS, "bad I2C bus"}, + {LG_BAD_I2C_ADDR, "bad I2C address"}, + {LG_BAD_SPI_CHANNEL, "bad SPI channel"}, + {LG_BAD_I2C_FLAGS, "bad I2C open flags"}, + {LG_BAD_SPI_FLAGS, "bad SPI open flags"}, + {LG_BAD_SERIAL_FLAGS, "bad serial open flags"}, + {LG_BAD_SPI_SPEED, "bad SPI speed"}, + {LG_BAD_SERIAL_DEVICE, "bad serial device name"}, + {LG_BAD_SERIAL_SPEED, "bad serial baud rate"}, + {LG_BAD_FILE_PARAM, "bad file parameter"}, + {LG_BAD_I2C_PARAM, "bad I2C parameter"}, + {LG_BAD_SERIAL_PARAM, "bad serial parameter"}, + {LG_I2C_WRITE_FAILED, "i2c write failed"}, + {LG_I2C_READ_FAILED, "i2c read failed"}, + {LG_BAD_SPI_COUNT, "bad SPI count"}, + {LG_SERIAL_WRITE_FAILED, "ser write failed"}, + {LG_SERIAL_READ_FAILED, "ser read failed"}, + {LG_SERIAL_READ_NO_DATA, "ser read no data available"}, + {LG_UNKNOWN_COMMAND, "unknown command"}, + {LG_SPI_XFER_FAILED, "spi xfer/read/write failed"}, + {LG_BAD_POINTER, "bad (NULL) pointer"}, + {LG_MSG_TOOBIG, "socket/pipe message too big"}, + {LG_BAD_MALLOC_MODE, "bad memory allocation mode"}, + {LG_TOO_MANY_SEGS, "too many I2C transaction segments"}, + {LG_BAD_I2C_SEG, "an I2C transaction segment failed"}, + {LG_BAD_SMBUS_CMD, "SMBus command not supported by driver"}, + {LG_BAD_I2C_WLEN, "bad I2C write length"}, + {LG_BAD_I2C_RLEN, "bad I2C read length"}, + {LG_BAD_I2C_CMD, "bad I2C command"}, + {LG_FILE_OPEN_FAILED, "file open failed"}, + {LG_BAD_FILE_MODE, "bad file mode"}, + {LG_BAD_FILE_FLAG, "bad file flag"}, + {LG_BAD_FILE_READ, "bad file read"}, + {LG_BAD_FILE_WRITE, "bad file write"}, + {LG_FILE_NOT_ROPEN, "file not open for read"}, + {LG_FILE_NOT_WOPEN, "file not open for write"}, + {LG_BAD_FILE_SEEK, "bad file seek"}, + {LG_NO_FILE_MATCH, "no files match pattern"}, + {LG_NO_FILE_ACCESS, "no permission to access file"}, + {LG_FILE_IS_A_DIR, "file is a directory"}, + {LG_BAD_SHELL_STATUS, "bad shell return status"}, + {LG_BAD_SCRIPT_NAME, "bad script name"}, + {LG_CMD_INTERRUPTED, "Python socket command interrupted"}, + {LG_BAD_EVENT_REQUEST, "bad event request"}, + {LG_BAD_GPIO_NUMBER, "bad GPIO number"}, + {LG_BAD_GROUP_SIZE, "bad group size"}, + {LG_BAD_LINEINFO_IOCTL, "bad lineinfo IOCTL"}, + {LG_BAD_READ, "bad GPIO read"}, + {LG_BAD_WRITE, "bad GPIO write"}, + {LG_CANNOT_OPEN_CHIP, "can not open gpiochip"}, + {LG_GPIO_BUSY, "GPIO busy"}, + {LG_GPIO_NOT_ALLOCATED, "GPIO not allocated"}, + {LG_NOT_A_GPIOCHIP, "not a gpiochip"}, + {LG_NOT_ENOUGH_MEMORY, "not enough memory"}, + {LG_POLL_FAILED, "GPIO poll failed"}, + {LG_TOO_MANY_GPIOS, "too many GPIO"}, + {LG_UNEGPECTED_ERROR, "unexpected error"}, + {LG_BAD_PWM_MICROS, "bad PWM micros"}, + {LG_NOT_GROUP_LEADER, "GPIO not the group leader"}, + {LG_SPI_IOCTL_FAILED, "SPI iOCTL failed"}, + {LG_BAD_GPIOCHIP, "bad gpiochip"}, + {LG_BAD_CHIPINFO_IOCTL, "bad chipinfo IOCTL"}, + {LG_BAD_CONFIG_FILE, "bad configuration file"}, + {LG_BAD_CONFIG_VALUE, "bad configuration value"}, + {LG_NO_PERMISSIONS, "no permission to perform action"}, + {LG_BAD_USERNAME, "bad user name"}, + {LG_BAD_SECRET, "bad secret for user"}, + {LG_TX_QUEUE_FULL, "TX queue full"}, + {LG_BAD_CONFIG_ID, "bad configuration id"}, + {LG_BAD_DEBOUNCE_MICS, "bad debounce microseconds"}, + {LG_BAD_WATCHDOG_MICS, "bad watchdog microseconds"}, + {LG_BAD_SERVO_FREQ, "bad servo frequency"}, + {LG_BAD_SERVO_WIDTH, "bad servo pulsewidth"}, + {LG_BAD_PWM_FREQ, "bad PWM frequency"}, + {LG_BAD_PWM_DUTY, "bad PWM dutycycle"}, + {LG_GPIO_NOT_AN_OUTPUT, "GPIO not set as an output"}, + {LG_INVALID_GROUP_ALERT, "can not set a group to alert"}, +}; + +const char *lgErrStr(int error) +{ + int i; + + for (i=0; i<(sizeof(xErrInfo)/sizeof(xErrInfo_t)); i++) + { + if (xErrInfo[i].error == error) return xErrInfo[i].str; + } + return "unknown error"; +} + diff --git a/lgExec.c b/lgExec.c new file mode 100644 index 0000000..5e1e3c3 --- /dev/null +++ b/lgExec.c @@ -0,0 +1,1169 @@ +/* +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 +*/ + +#include +#include +#include +#include +#include + +#include "lgpio.h" +#include "rgpiod.h" + +#include "lgCfg.h" +#include "lgCmd.h" +#include "lgCtx.h" +#include "lgDbg.h" +#include "lgHdl.h" +#include "lgMD5.h" + +typedef enum +{ + DEV, + DEVNEXT, + DEV2, + SUBDEV, + SUBDEVNEXT, + SUBDEV2, + DOT, + COMMA, +} checkDevSubdev_t; + +static lgCfg_p Cfg; + +static pthread_once_t xInited = PTHREAD_ONCE_INIT; + +static uint64_t xMakeSalt(void) +{ + struct timespec xts; + + clock_gettime(CLOCK_REALTIME, &xts); + + return ((xts.tv_sec + xts.tv_nsec) * random()) + (random() + xts.tv_nsec); +} + +static int xLoadConfig(void) +{ + char cfgFile[LG_MAX_PATH]; + + snprintf(cfgFile, LG_MAX_PATH, "%s/permits", lguGetConfigDir()); + + if (Cfg) lgCfgFree(Cfg); + + Cfg = lgCfgRead(cfgFile); + + if (Cfg) lgCfgPrint(Cfg, stderr); + + if (Cfg) return LG_OKAY; else return LG_BAD_CONFIG_FILE; +} + +static void xInit(void) +{ + xLoadConfig(); +} + +int xCheckDevSubdev(char *str, int dev, int subdev) +{ + int num; + int r1=0, r2=0; + checkDevSubdev_t expect=DEV; + + while (1) + { + printf("%c", *str); + switch (*str) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + + if ((expect == DEV) || (expect == SUBDEV)) + { + num = 0; + while (isdigit(*str)) + {num = (num * 10) + (*str) - '0'; ++str;} + --str; + r1 = num; + + if (expect == DEV) + { + if ((dev == r1) && (subdev < 0)) return 1; + expect = DEVNEXT; + } + else + { + if (subdev == r1) return 1; + expect = SUBDEVNEXT; + } + } + + else if ((expect == DEV2) || (expect == SUBDEV2)) + { + num = 0; + while (isdigit(*str)) + {num = (num * 10) + (*str) - '0'; ++str;} + --str; + r2 = num; + + if (expect == DEV2) + { + if ((dev < r1) || (dev > r2)) return 0; /* fail */ + if (subdev < 0) return 1; /*pass */ + expect = DOT; + } + else + { + if ((subdev >= r1) && (subdev <= r2)) return 1; + expect = COMMA; + } + } + + else return 0; + + break; + + case '\0': + return 0; + break; + + case ' ': + case '\t': + break; + + case '*': + if (expect == DEV) + { + if (subdev < 0) return 1; /* pass */ + expect = DOT; + } + + else if (expect == SUBDEV) return 1; + + else return 0; + + break; + + case '-': + + if (expect == DEVNEXT) expect = DEV2; + else if (expect == SUBDEVNEXT) expect = SUBDEV2; + else return 0; + + break; + + case '.': + + if (expect == DOT) expect = SUBDEV; + + else if (expect == DEVNEXT) + { + if (dev != r1) return 0; /* can't pass */ + expect = SUBDEV; + } + + else return 0; + + break; + + case ',': + + if ((expect == COMMA) || (expect == SUBDEVNEXT)) + { + expect = SUBDEV; + } + else return 0; + + break; + + default: + return 0; + } + ++str; + } + + return 0; +} + +static int xCheckDebugPermissions(lgCtx_p Ctx) +{ + if (Ctx->permits.debug) + { + if ((strcmp(Ctx->permits.debug, "Y") == 0) || + (strcmp(Ctx->permits.debug, "y") == 0)) + return 1; + } + return 0; +} + +static int xCheckShellPermissions(lgCtx_p Ctx) +{ + if (Ctx->permits.shell) + { + if ((strcmp(Ctx->permits.shell, "Y") == 0) || + (strcmp(Ctx->permits.shell, "y") == 0)) + return 1; + } + return 0; +} + +static int xCheckNotifyPermissions(lgCtx_p Ctx) +{ + if (Ctx->permits.notify) + { + if ((strcmp(Ctx->permits.notify, "Y") == 0) || + (strcmp(Ctx->permits.notify, "y") == 0)) + return 1; + } + return 0; +} + + +static int xCheckScriptPermissions(lgCtx_p Ctx) +{ + if (Ctx->permits.scripts) + { + if ((strcmp(Ctx->permits.scripts, "Y") == 0) || + (strcmp(Ctx->permits.scripts, "y") == 0)) + return 1; + } + return 0; +} + + +static int xCheckSerialPermissions(lgCtx_p Ctx, char *serDev) +{ + char buf[LG_MAX_PATH]; + char *str, *pos, *token; + const char *delim = ":"; + + if (!Ctx->permits.serial) return 0; + + strncpy(buf, Ctx->permits.serial, sizeof(buf)-1); + buf[sizeof(buf)-1] = 0; + + str = buf; + while ((token=lgCfgNextToken(&str, delim, &pos))) + { + LG_DBG(LG_DEBUG_ALWAYS, "%s and %s", serDev, token); + + if (fnmatch(token, serDev, 0) == 0) return 1; + } + return 0; +} + +static int xCheckI2cPermissions(lgCtx_p Ctx, int i2cDev, int i2cAddr) +{ + char buf[LG_MAX_PATH]; + char *str, *pos, *token; + const char *delim = ":"; + + if (!Ctx->permits.i2c) return 0; + + strncpy(buf, Ctx->permits.i2c, sizeof(buf)-1); + buf[sizeof(buf)-1] = 0; + + str = buf; + while ((token=lgCfgNextToken(&str, delim, &pos))) + { + LG_DBG(LG_DEBUG_ALWAYS, "%d.%d and %s", i2cDev, i2cAddr, token); + + if (xCheckDevSubdev(token, i2cDev, i2cAddr)) return 1; + } + return 0; +} + +static int xCheckSpiPermissions(lgCtx_p Ctx, int spiDev, int spiChan) +{ + char buf[LG_MAX_PATH]; + char *str, *pos, *token; + const char *delim = ":"; + + if (!Ctx->permits.spi) return 0; + + strncpy(buf, Ctx->permits.spi, sizeof(buf)-1); + buf[sizeof(buf)-1] = 0; + + str = buf; + while ((token=lgCfgNextToken(&str, delim, &pos))) + { + LG_DBG(LG_DEBUG_ALWAYS, "%d.%d and %s", spiDev, spiChan, token); + + if (xCheckDevSubdev(token, spiDev, spiChan)) return 1; + } + return 0; +} + +static int xCheckGpioPermissions(lgCtx_p Ctx, int gpioDev, int gpio) +{ + char buf[LG_MAX_PATH]; + char *str, *pos, *token; + const char *delim = ":"; + + if (!Ctx->permits.gpio) return 0; + + strncpy(buf, Ctx->permits.gpio, sizeof(buf)-1); + buf[sizeof(buf)-1] = 0; + + str = buf; + while ((token=lgCfgNextToken(&str, delim, &pos))) + { + LG_DBG(LG_DEBUG_ALWAYS, "%d.%d and %s", gpioDev, gpio, token); + + if (xCheckDevSubdev(token, gpioDev, gpio)) return 1; + } + return 0; +} + +static int xSetGpioPermissions(lgCtx_p Ctx, int gpioDev, int handle) +{ + lgChipInfo_t chipInfo; + int i, banned; + + if (lgGpioGetChipInfo(handle, &chipInfo) == 0) + { + for (i=0; ipermits.files) return 0; + + if (xPathBad(filename)) return 0; + + mode &= LG_FILE_RW; + + if (!mode) return 0; + + buffer[0] = 0; + match[0] = 0; + + strncpy(paths, Ctx->permits.files, sizeof(paths)-1); + paths[sizeof(paths)-1] = 0; + + str = paths; + while ((token=lgCfgNextToken(&str, delim, &pos))) + { + LG_DBG(LG_DEBUG_ALWAYS, "token: '%s'", token); + buffer[0] = 0; + perm = 0; + term = 0; + sscanf(token, " %1000s %c%c", buffer, &perm, &term); + + if (term != 0) + { + LG_DBG(LG_DEBUG_ALWAYS, "ignored, bad termination: %s", token); + continue; + } + + if (xPathBad(buffer)) + { + LG_DBG(LG_DEBUG_ALWAYS, "ignored, risky: %s", token); + continue; + } + + if (fnmatch(buffer, filename, 0) == 0) + { + LG_DBG(LG_DEBUG_ALWAYS, "[%s] matches %s", buffer, filename); + if (match[0]) // prior match? + { + if (fnmatch(match, buffer, 0) == 0) // more precise match? + { + LG_DBG(LG_DEBUG_ALWAYS, "%s matches [%s]", match, buffer); + strcpy(match, buffer); // most precise match so far + mperm = perm; + } + } + else // no prior match + { + strcpy(match, buffer); + mperm = perm; + } + } + } + + if (match[0]) + { + switch (toupper(mperm)) + { + case 'R': + if (mode == LG_FILE_READ) approve = 1; + break; + case 'W': + if (mode == LG_FILE_WRITE) approve = 1; + break; + case 'U': + approve = 1; + break; + } + } + + return approve; +} + +static void xClearUserPermits(lgCtx_p Ctx) +{ + memset(&Ctx->permits, 0, sizeof(Ctx->permits)); +} + +static void xSetUserPermits(lgCtx_p Ctx) +{ + if (Cfg) + { + Ctx->permits.files = lgCfgGetValue(Cfg, "files", Ctx->user); + Ctx->permits.scripts = lgCfgGetValue(Cfg, "scripts", Ctx->user); + Ctx->permits.i2c = lgCfgGetValue(Cfg, "i2c", Ctx->user); + Ctx->permits.spi = lgCfgGetValue(Cfg, "spi", Ctx->user); + Ctx->permits.serial = lgCfgGetValue(Cfg, "serial", Ctx->user); + Ctx->permits.gpio = lgCfgGetValue(Cfg, "gpio", Ctx->user); + Ctx->permits.notify = lgCfgGetValue(Cfg, "notify", Ctx->user); + Ctx->permits.debug = lgCfgGetValue(Cfg, "debug", Ctx->user); + Ctx->permits.shell = lgCfgGetValue(Cfg, "shell", Ctx->user); + } + else xClearUserPermits(Ctx); +} + +static int xSetUser(lgCtx_p Ctx, char *user, char *buf) +{ + int result = LG_BAD_USERNAME; + + LG_DBG(LG_DEBUG_TRACE, "user=%s", user); + + if (strlen(user)) + { + if (gPermits) + { + Ctx->approved = 0; + xClearUserPermits(Ctx); + } + snprintf(Ctx->salt1, LG_SALT_LEN, "%s", user); + snprintf(Ctx->user, LG_USER_LEN, "%s", user+LG_SALT_LEN); + snprintf(Ctx->salt2, LG_SALT_LEN, "%015"PRIx64, xMakeSalt()); + strcpy(buf, Ctx->salt2); + result = LG_SALT_LEN; + } + + return result; +} + +static int xShareSetUse(lgCtx_p Ctx, int share) +{ + LG_DBG(LG_DEBUG_TRACE, "share=%d", share); + + Ctx->autoSetShare = share; + Ctx->autoUseShare = share; + + return LG_OKAY; +} + +static int xShareUse(lgCtx_p Ctx, int share) +{ + LG_DBG(LG_DEBUG_TRACE, "share=%d", share); + + Ctx->autoUseShare = share; + + return LG_OKAY; +} + +static int xPassword(lgCtx_p Ctx, char *hash) +{ + int result; + char myHash[34]; + char secretFile[LG_MAX_PATH]; + + result = LG_BAD_SECRET; + + LG_DBG(LG_DEBUG_TRACE, "hash=%s", hash); + + if (!gPermits) return LG_OKAY; + + if (strcmp(Ctx->user, LG_DEFAULT_USER) == 0) + { + /* default user is a special case */ + Ctx->approved = 1; + xSetUserPermits(Ctx); + return LG_OKAY; + } + + if ((strlen(lguGetConfigDir()) + strlen("/.lg_secret")) < LG_MAX_PATH) + { + sprintf(secretFile, "%s/.lg_secret", lguGetConfigDir()); + } + else return result; + + lgMd5UserHash(Ctx->user, Ctx->salt1, Ctx->salt2, secretFile, myHash); + + LG_DBG(LG_DEBUG_TRACE, "myhash=%s", myHash); + + if (strcmp(hash, myHash) == 0) + { + result = LG_OKAY; + Ctx->approved = 1; + xSetUserPermits(Ctx); + } + return result; +} + +int lgExecCmd(lgCmd_p cmdP, int cmdBufSize) +{ + static int xPid = 0; + int res; + uint32_t tmp1; + int i; + int size; + lgCtx_p Ctx; + lgLineInfo_t lInfo; + lgChipInfo_t cInfo; + res = LG_OKAY; + char *cmdExt=(char*)&cmdP[1]; + uint32_t *argI=(uint32_t*)&cmdP[1]; + uint64_t *argQ=(uint64_t*)&cmdP[1]; + + pthread_once(&xInited, xInit); + + Ctx = lgCtxGet(); + + if (Ctx == NULL) return LG_NO_MEMORY; + + if (Ctx->owner == 0) + { + Ctx->owner = ++xPid; + + /* set default user if no preset user */ + if (!strlen(Ctx->user)) + strncpy(Ctx->user, LG_DEFAULT_USER, LG_USER_LEN); + + /* set permissions for user */ + Ctx->approved = 1; + xSetUserPermits(Ctx); + } + + size = cmdP->size; + + cmdP->size = 0; + + if (size) cmdExt[size] = 0; /* terminate trailing string */ + + cmdBufSize -= sizeof(lgCmd_t); + +/* + fprintf(stderr, "owner=%d cmd=%d m0=%d m1=%d m2=%d m3=%d\n", + Ctx->owner, cmdP->cmd, argI[0], argI[1], argI[2], argI[3]); +*/ + + switch (cmdP->cmd) + { + case LG_CMD_TICK: + argQ[0] = lguTimestamp(); + cmdP->size = 8; + res = 8; + break; + + case LG_CMD_CGI: + if (!gPermits || xCheckDebugPermissions(Ctx)) + { + res = lguGetInternal(argI[0], &argQ[0]); + if (res == LG_OKAY) + { + cmdP->size = 8; + res = 8; + } + } + else + res = LG_NO_PERMISSIONS; + break; + + case LG_CMD_CSI: + if (!gPermits || xCheckDebugPermissions(Ctx)) + res = lguSetInternal(argI[2], argQ[0]); + else + res = LG_NO_PERMISSIONS; + break; + + case LG_CMD_FC: res = lgFileClose(argI[0]); break; + + case LG_CMD_FL: + res = LG_NO_FILE_ACCESS; + + if (xPathBad(cmdExt+4)) break; + + if (!gPermits || xApprove(Ctx, cmdExt+4, LG_FILE_READ)) + { + if (argI[0] > (cmdBufSize-4)) argI[0] = cmdBufSize-4; + res = lgFileList(cmdExt+4, cmdExt, argI[0]); + if (res > 0) cmdP->size = res; + } + break; + + case LG_CMD_FO: + res = LG_NO_FILE_ACCESS; + + if (xPathBad(cmdExt+4)) break; + + if (!gPermits || xApprove(Ctx, cmdExt+4, argI[0])) + { + res = lgFileOpen(cmdExt+4, argI[0]); + } + break; + + case LG_CMD_FR: + if (argI[1] > cmdBufSize) argI[1] = cmdBufSize; + res = lgFileRead(argI[0], cmdExt, argI[1]); + if (res > 0) cmdP->size = res; + break; + + case LG_CMD_FREE: + lgHdlPurgeByOwner(Ctx->owner); + res = 0; + break; + + case LG_CMD_FS: + res = lgFileSeek(argI[0], argI[1], argI[2]); + break; + + case LG_CMD_FW: res = lgFileWrite(argI[0], cmdExt+4, size-4); break; + + case LG_CMD_I2CC: res = lgI2cClose(argI[0]); break; + + case LG_CMD_I2CO: + if (!gPermits || xCheckI2cPermissions(Ctx, argI[0], argI[1])) + res = lgI2cOpen(argI[0], argI[1], argI[2]); + else + res = LG_NO_PERMISSIONS; + break; + + case LG_CMD_I2CPC: + res = lgI2cProcessCall(argI[0], argI[1], argI[2]); + break; + + case LG_CMD_I2CPK: + res = lgI2cBlockProcessCall(argI[0], argI[1], cmdExt+8, size-8); + if (res > 0) + { + memcpy(cmdExt, cmdExt+8, res); + cmdP->size = res; + } + break; + + case LG_CMD_I2CRB: res = lgI2cReadByteData(argI[0], argI[1]); break; + + case LG_CMD_I2CRD: + if (argI[1] > cmdBufSize) argI[1] = cmdBufSize; + res = lgI2cReadDevice(argI[0], cmdExt, argI[1]); + if (res > 0) cmdP->size = res; + break; + + case LG_CMD_I2CRI: + res = lgI2cReadI2CBlockData(argI[0], argI[1], cmdExt, argI[2]); + if (res > 0) cmdP->size = res; + break; + + case LG_CMD_I2CRK: + res = lgI2cReadBlockData(argI[0], argI[1], cmdExt); + if (res > 0) cmdP->size = res; + break; + + case LG_CMD_I2CRS: res = lgI2cReadByte(argI[0]); break; + + case LG_CMD_I2CRW: res = lgI2cReadWordData(argI[0], argI[1]); break; + + case LG_CMD_I2CWB: + res = lgI2cWriteByteData(argI[0], argI[1], argI[2]); + break; + + case LG_CMD_I2CWD: + res = lgI2cWriteDevice(argI[0], cmdExt+4, size-4); + break; + + case LG_CMD_I2CWI: + res = lgI2cWriteI2CBlockData(argI[0], argI[1], cmdExt+8, size-8); + break; + + case LG_CMD_I2CWK: + res = lgI2cWriteBlockData(argI[0], argI[1], cmdExt+8, size-8); + break; + + case LG_CMD_I2CWQ: res = lgI2cWriteQuick(argI[0], argI[1]); break; + + case LG_CMD_I2CWS: res = lgI2cWriteByte(argI[0], argI[1]); break; + + case LG_CMD_I2CWW: + res = lgI2cWriteWordData(argI[0], argI[1], argI[2]); + break; + + case LG_CMD_I2CZ: + if (size < cmdBufSize) + { + tmp1 = cmdBufSize - size; + res = lgI2cZip(argI[0], cmdExt+4, size-4, cmdExt+size, tmp1); + if (res > 0) + { + memcpy(cmdExt, cmdExt+size, res); + cmdP->size = res; + } + } + else res = LG_BAD_I2C_WLEN; + break; + + case LG_CMD_LCFG: + if (!gPermits || xCheckDebugPermissions(Ctx)) + res = xLoadConfig(); + else + res = LG_NO_PERMISSIONS; + break; + + case LG_CMD_MICS: + if (argI[0] <= LG_MAX_MICS_DELAY) lguSleep((double)argI[0]/1E6); + else res = LG_BAD_MICS_DELAY; + break; + + case LG_CMD_MILS: + if (argI[0] <= LG_MAX_MILS_DELAY) lguSleep((double)argI[0]/1E3); + else res = LG_BAD_MILS_DELAY; + break; + + case LG_CMD_NR: res = lgNotifyResume(argI[0]); break; + + case LG_CMD_NC: res = lgNotifyClose(argI[0]); break; + + case LG_CMD_NO: + if (!gPermits || xCheckNotifyPermissions(Ctx)) + res = lgNotifyOpen(); + else + res = LG_NO_PERMISSIONS; + break; + + case LG_CMD_NOIB: + res = lgNotifyOpenInBand(argI[0]); + break; + + case LG_CMD_NP: res = lgNotifyPause(argI[0]); break; + + case LG_CMD_LGV: res = lguVersion(); break; + + case LG_CMD_PCD: + strcpy(cmdExt, lguGetConfigDir()); + res = strlen(cmdExt); + if (res > 0) cmdP->size = res; + break; + + case LG_CMD_PROC: + if (!gPermits || xCheckScriptPermissions(Ctx)) + res = lgScriptStore(cmdExt); + else + res = LG_NO_PERMISSIONS; + break; + + case LG_CMD_PROCD: res = lgScriptDelete(argI[0]); break; + + case LG_CMD_PROCP: + res = lgScriptStatus(argI[0], (uint32_t *)(cmdExt+4)); + if (res >= 0) + { + argI[0] = res; + res = 4 + (4*LG_MAX_SCRIPT_PARAMS); + cmdP->size = res; + } + break; + + case LG_CMD_PROCR: + res = lgScriptRun(argI[0], (size/4)-1, &argI[1]); + break; + + case LG_CMD_PROCS: res = lgScriptStop(argI[0]); break; + + case LG_CMD_PROCU: + res = lgScriptUpdate(argI[0], (size/4)-1, &argI[1]); + break; + + case LG_CMD_PWD: + strcpy(cmdExt, lguGetWorkDir()); + res = strlen(cmdExt); + if (res > 0) cmdP->size = res; + break; + + case LG_CMD_SBC: + res = lguSbcName(cmdExt, cmdBufSize); + if (res > 0) cmdP->size = res; + break; + + case LG_CMD_SERC: res = lgSerialClose(argI[0]); break; + + case LG_CMD_SERDA: res = lgSerialDataAvailable(argI[0]); break; + + case LG_CMD_SERO: + if (!gPermits || xCheckSerialPermissions(Ctx, cmdExt+8)) + res = lgSerialOpen(cmdExt+8, argI[0], argI[1]); + else + res = LG_NO_PERMISSIONS; + break; + + case LG_CMD_SERR: + if (argI[1] > cmdBufSize) argI[1] = cmdBufSize; + res = lgSerialRead(argI[0], cmdExt, argI[1]); + if (res > 0) cmdP->size = res; + break; + + case LG_CMD_SERRB: res = lgSerialReadByte(argI[0]); break; + + case LG_CMD_SERW: res = lgSerialWrite(argI[0], cmdExt+4, size-4); break; + + case LG_CMD_SERWB: res = lgSerialWriteByte(argI[0], argI[1]); break; + + case LG_CMD_SHELL: + if (!gPermits || xCheckShellPermissions(Ctx)) + res = lgShell(cmdExt+4, cmdExt+4+argI[0]); + else + res = LG_NO_PERMISSIONS; + break; + + case LG_CMD_SPIC: + res = lgSpiClose(argI[0]); + break; + + case LG_CMD_SPIO: + if (!gPermits || xCheckSpiPermissions(Ctx, argI[0], argI[1])) + res = lgSpiOpen(argI[0], argI[1], argI[2], argI[3]); + else + res = LG_NO_PERMISSIONS; + break; + + case LG_CMD_SPIR: + if (argI[1] > cmdBufSize) argI[1] = cmdBufSize; + res = lgSpiRead(argI[0], cmdExt, argI[1]); + if (res > 0) cmdP->size = res; + break; + + case LG_CMD_SPIW: + if (size < cmdBufSize) + res = lgSpiWrite(argI[0], cmdExt+4, size-4); + else + res = LG_BAD_SPI_COUNT; + break; + + case LG_CMD_SPIX: + if (size < cmdBufSize) + { + res = lgSpiXfer(argI[0], cmdExt+4, cmdExt, size-4); + if (res > 0) cmdP->size = res; + } + else + res = LG_BAD_SPI_COUNT; + break; + + case LG_CMD_USER: + res = xSetUser(Ctx, cmdExt, cmdExt); + if (res > 0) cmdP->size = res; + break; + + case LG_CMD_PASSW: + res = xPassword(Ctx, cmdExt); + break; + + case LG_CMD_SHARE: // share + res = xShareSetUse(Ctx, argI[0]); + break; + + case LG_CMD_SHRS: // h share + res = lgHdlSetShare(argI[0], argI[1]); + break; + + case LG_CMD_SHRU: // share + res = xShareUse(Ctx, argI[0]); + break; + + case LG_CMD_GC: + // handle + res = lgGpiochipClose(argI[0]); break; + + case LG_CMD_GO: + // gpioDev + if (!gPermits || xCheckGpioPermissions(Ctx, argI[0], -1)) + { + res = lgGpiochipOpen(argI[0]); + if (res >= 0) + { + /* have handle, initialise permissions */ + if (gPermits) xSetGpioPermissions(Ctx, argI[0], res); + } + } + else + res = LG_NO_PERMISSIONS; + break; + + case LG_CMD_GR: + // handle gpio + res = lgGpioRead(argI[0], argI[1]); + break; + + case LG_CMD_GW: + // handle gpio value + res = lgGpioWrite(argI[0], argI[1], argI[2]); + break; + + case LG_CMD_GDEB: + // handle gpio value + res = lgGpioSetDebounce(argI[0], argI[1], argI[2]); + break; + + case LG_CMD_GWDOG: + // handle gpio value + res = lgGpioSetWatchdog(argI[0], argI[1], argI[2]); + break; + + case LG_CMD_GSI: + // handle gpio + res = lgGpioClaimInput(argI[0], 0, argI[1]); + break; + + case LG_CMD_GSIX: + // handle lFlags gpio + res = lgGpioClaimInput(argI[0], argI[1], argI[2]); + break; + + case LG_CMD_GSGI: + // handle size *gpios + tmp1 = (size/4)-1; + res = lgGroupClaimInput( + argI[0], 0, tmp1, (const int *)argI+1); + break; + + case LG_CMD_GSGIX: + // handle lFlags size *gpios + tmp1 = (size/4)-2; + res = lgGroupClaimInput( + argI[0], argI[1], tmp1, (const int *)argI+2); + break; + + case LG_CMD_GSO: + // handle gpio + res = lgGpioClaimOutput(argI[0], 0, argI[1], 0); + break; + + case LG_CMD_GSOX: + // handle lFlags gpio value + res = lgGpioClaimOutput(argI[0], argI[1], argI[2], argI[3]); + break; + + case LG_CMD_GSGO: + // handle size *gpios + tmp1 = (size/4)-1; + for (i=0; i= 0) + { + argI[2] = res; // group size + res = 12; + cmdP->size = res; + } + break; + + case LG_CMD_GGW: + // bitsQ handle group + res = lgGroupWrite(argI[2], argI[3], argQ[0], -1); + break; + + case LG_CMD_GGWX: + // bitsQ maskQ handle group + res = lgGroupWrite(argI[4], argI[5], argQ[0], argQ[1]); + break; + + case LG_CMD_GSA: + // handle gpio nfyh + res = lgGpioClaimAlert(argI[0], 0, LG_BOTH_EDGES, argI[1], argI[2]); + break; + + case LG_CMD_GSAX: + // handle lFlags eFlags gpio nfyh + res = lgGpioClaimAlert(argI[0], argI[1], argI[2], argI[3], argI[4]); + break; + + case LG_CMD_GP: + // handle gpio micros_on micros_off + res = lgTxPulse( + argI[0], argI[1], argI[2], argI[3], 0, 0); + break; + + case LG_CMD_GPX: + // handle gpio micros_on micros_off offset cycles + res = lgTxPulse( + argI[0], argI[1], argI[2], argI[3], argI[4], argI[5]); + break; + + case LG_CMD_P: + // handle gpio freq dutyc + // freq and dutyc are received as 1000 times value + res = lgTxPwm( + argI[0], argI[1], argI[2]/1000.0, argI[3]/1000.0, 0, 0); + break; + + case LG_CMD_PX: + // handle gpio freq dutyc offset cycles + // freq and dutyc are received as 1000 times value + res = lgTxPwm( + argI[0], argI[1], argI[2]/1000.0, argI[3]/1000.0, + argI[4], argI[5]); + break; + + case LG_CMD_S: + // handle gpio width + res = lgTxServo( + argI[0], argI[1], argI[2], 50, 0, 0); + break; + + case LG_CMD_SX: + // handle gpio width freq offset cycles + res = lgTxServo( + argI[0], argI[1], argI[2], argI[3], argI[4], argI[5]); + break; + + case LG_CMD_GWAVE: + // pulseQ* handle gpio + tmp1 = (size-8)/24; + res = lgTxWave( + argI[tmp1*6], argI[(tmp1*6)+1], tmp1, (lgPulse_p)&argQ[0]); + break; + + case LG_CMD_GMODE: + // handle gpio + res = lgGpioGetMode(argI[0], argI[1]); + break; + + case LG_CMD_GBUSY: + // handle gpio type + res = lgTxBusy(argI[0], argI[1], argI[2]); + break; + + case LG_CMD_GROOM: + // handle gpio type + res = lgTxRoom(argI[0], argI[1], argI[2]); + break; + + case LG_CMD_GSF: + // handle gpio + res = lgGpioFree(argI[0], argI[1]); + break; + + case LG_CMD_GSGF: + // handle gpio + res = lgGroupFree(argI[0], argI[1]); + break; + + case LG_CMD_GIC: + // handle + res = lgGpioGetChipInfo(argI[0], &cInfo); + if (res == LG_OKAY) + { + argI[0] = cInfo.lines; + + memcpy(cmdExt+4, &cInfo.name, sizeof(cInfo.name)); + + memcpy(cmdExt+4+sizeof(cInfo.name), + &cInfo.label, sizeof(cInfo.label)); + + res = 4 + sizeof(cInfo.name) + sizeof(cInfo.label); + cmdP->size = res; + } + + break; + + case LG_CMD_GIL: + // handle gpio + res = lgGpioGetLineInfo(argI[0], argI[1], &lInfo); + if (res == LG_OKAY) + { + argI[0] = lInfo.offset; + argI[1] = lInfo.lFlags; + + memcpy(cmdExt+8, &lInfo.name, sizeof(lInfo.name)); + + memcpy(cmdExt+8+sizeof(lInfo.name), + &lInfo.user, sizeof(lInfo.user)); + + res = 8 + sizeof(lInfo.name) + sizeof(lInfo.user); + cmdP->size = res; + } + break; + + default: + res = LG_UNKNOWN_COMMAND; + break; + } + + cmdP->status = res; + + return res; +} + diff --git a/lgFile.c b/lgFile.c new file mode 100644 index 0000000..c72ebf3 --- /dev/null +++ b/lgFile.c @@ -0,0 +1,317 @@ +/* +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 +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "lgpio.h" +#include "rgpiod.h" + +#include "lgDbg.h" +#include "lgHdl.h" + +typedef struct +{ + int16_t fd; + uint32_t mode; +} lgFileObj_t, *lgFileObj_p; + +static void _fileClose(lgFileObj_p file) +{ + if (file) close(file->fd); +} + +int lgFileOpen(char *file, int mode) +{ + int fd=-1; + int handle, oflag, omode; + struct stat statbuf; + lgFileObj_p h; + + LG_DBG(LG_DEBUG_TRACE, "file=%s mode=%d", file, mode); + + if ( (mode < LG_FILE_MIN) || + (mode > LG_FILE_MAX) || + ((mode & LG_FILE_RW) == 0) ) + PARAM_ERROR(LG_BAD_FILE_MODE, "bad mode (%d)", mode); + + if ((mode > 3) && ((mode & LG_FILE_WRITE) == 0)) + PARAM_ERROR(LG_NO_FILE_ACCESS, "no permission to write file (%s)", file); + + omode = 0; + oflag = 0; + + if (mode & LG_FILE_APPEND) + { + oflag |= O_APPEND; + } + + if (mode & LG_FILE_CREATE) + { + oflag |= O_CREAT; + omode |= (S_IRUSR|S_IWUSR); + } + + if (mode & LG_FILE_TRUNC) + { + oflag |= O_TRUNC; + } + + switch(mode&LG_FILE_RW) + { + case LG_FILE_READ: + fd = open(file, O_RDONLY|oflag, omode); + break; + + case LG_FILE_WRITE: + fd = open(file, O_WRONLY|oflag, omode); + break; + + case LG_FILE_RW: + fd = open(file, O_RDWR|oflag, omode); + break; + } + + if (fd == -1) + { + return LG_FILE_OPEN_FAILED; + } + else + { + if (stat(file, &statbuf) == 0) + { + if (S_ISDIR(statbuf.st_mode)) + { + close(fd); + PARAM_ERROR(LG_FILE_IS_A_DIR, "file is a directory (%s)", file); + } + } + } + + handle = lgHdlAlloc( + LG_HDL_TYPE_FILE, sizeof(lgFileObj_t), (void**)&h, _fileClose); + + if (handle < 0) + { + close(fd); + return LG_NO_MEMORY; + } + + h->fd = fd; + h->mode = mode; + + return handle; +} + +int lgFileClose(int handle) +{ + int status; + lgFileObj_p h; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d", handle); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_FILE, (void **)&h); + + if (status == LG_OKAY) + { + status = lgHdlFree(handle, LG_HDL_TYPE_FILE); + + lgHdlUnlock(handle); + } + + return status; +} + +int lgFileWrite(int handle, char *buf, int count) +{ + int w; + int status; + lgFileObj_p h; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d count=%d [%s]", + handle, count, lgDbgBuf2Str(count, buf)); + + if (!count) + PARAM_ERROR(LG_BAD_FILE_PARAM, "bad count (%d)", count); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_FILE, (void **)&h); + + if (status == LG_OKAY) + { + if (h->mode & LG_FILE_WRITE) + { + w = write(h->fd, buf, count); + + if (w != count) + { + if (w == -1) + { + LG_DBG(LG_DEBUG_FILE, "write failed with errno %d", errno); + } + status = LG_BAD_FILE_WRITE; + } + } + else + { + LG_DBG(LG_DEBUG_FILE, "file not opened for write"); + status = LG_FILE_NOT_WOPEN; + } + + lgHdlUnlock(handle); + } + + return status; +} + +int lgFileRead(int handle, char *buf, int count) +{ + int status; + lgFileObj_p h; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d count=%d buf=%p", handle, count, buf); + + if (!count) + PARAM_ERROR(LG_BAD_FILE_PARAM, "bad count (%d)", count); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_FILE, (void **)&h); + + if (status == LG_OKAY) + { + if (h->mode & LG_FILE_READ) + { + status = read(h->fd, buf, count); + + if (status == -1) + { + LG_DBG(LG_DEBUG_FILE, "read failed with errno %d", errno); + status = LG_BAD_FILE_READ; + } + else + { + buf[status] = 0; + } + } + else + { + LG_DBG(LG_DEBUG_FILE, "file not opened for read"); + status = LG_FILE_NOT_ROPEN; + } + + lgHdlUnlock(handle); + } + + return status; +} + + +int lgFileSeek(int handle, int32_t seekOffset, int seekFrom) +{ + int whence; + int status; + lgFileObj_p h; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d offset=%d from=%d", + handle, seekOffset, seekFrom); + + switch (seekFrom) + { + case LG_FROM_START: + whence = SEEK_SET; + break; + + case LG_FROM_CURRENT: + whence = SEEK_CUR; + break; + + case LG_FROM_END: + whence = SEEK_END; + break; + + default: + PARAM_ERROR(LG_BAD_FILE_SEEK, "bad seek from (%d)", seekFrom); + } + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_FILE, (void **)&h); + + if (status == LG_OKAY) + { + status = lseek(h->fd, seekOffset, whence); + + if (status == -1) + { + LG_DBG(LG_DEBUG_FILE, "seek failed with errno %d", errno); + status = LG_BAD_FILE_SEEK; + } + + lgHdlUnlock(handle); + } + + return status; +} + +int lgFileList(char *fpat, char *buf, int count) +{ + int len, bufpos; + glob_t pglob; + int i; + + LG_DBG(LG_DEBUG_TRACE, "fpat=%s count=%d buf=%p", fpat, count, buf); + + bufpos = 0; + + if (glob(fpat, GLOB_MARK, NULL, &pglob) == 0) + { + for (i=0; i +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lgpio.h" + +#include "lgDbg.h" +#include "lgGpio.h" +#include "lgHdl.h" +#include "lgPthAlerts.h" +#include "lgPthTx.h" + +#define LG_CHIP_MODE_UNKNOWN 0 + +#define LG_CHIP_BIT_INPUT (1<<0) +#define LG_CHIP_BIT_OUTPUT (1<<1) +#define LG_CHIP_BIT_ALERT (1<<2) +#define LG_CHIP_BIT_GROUP (1<<3) + +void xWrite(lgChipObj_p chip, int gpio, int value); + +callbk_t lgGpioSamplesFunc = NULL; +void *lgGpioSamplesUserdata = NULL; + +static void _lgGpiochipClose(void *objPtr) +{ + int i; + lgChipObj_p chip; + + chip = objPtr; + + if (chip == NULL) return; + + /* stop any PWM on chip */ + lgPthTxStop(chip); + + /* stop any event reads on chip */ + lgPthAlertStop(chip); + + usleep(100000); /* should be long enough for PWM/events to stop */ + + for (i=0; ilines; i++) + { + if (chip->LineInf[i].mode != LG_CHIP_MODE_UNKNOWN) + { + /* free GPIO */ + LG_DBG(LG_DEBUG_ALLOC, "free GPIO: %d mode %d (%d of %d)", + i, + chip->LineInf[i].mode, + chip->LineInf[i].offset+1, + chip->LineInf[i].group_size); + + if (chip->LineInf[i].offset == 0) + { + /* group leader - remove all group resources */ + + if (chip->LineInf[i].fd != -1) + { + LG_DBG(LG_DEBUG_ALLOC, "close fd: %d", chip->LineInf[i].fd); + close(chip->LineInf[i].fd); + chip->LineInf[i].fd = -1; + } + + if (chip->LineInf[i].offsets) + { + LG_DBG(LG_DEBUG_ALLOC, "free offsets: *%p", + (void*)chip->LineInf[i].offsets); + free(chip->LineInf[i].offsets); + chip->LineInf[i].offsets = NULL; + } + + if (chip->LineInf[i].values) + { + LG_DBG(LG_DEBUG_ALLOC, "free values: *%p", + (void*)chip->LineInf[i].values); + + free(chip->LineInf[i].values); + chip->LineInf[i].values = NULL; + } + } + } + } + + LG_DBG(LG_DEBUG_ALLOC, "free LineInf: *%p", (void*)chip->LineInf); + + if (chip->LineInf) free(chip->LineInf); + + LG_DBG(LG_DEBUG_ALLOC, "close chip fd: %d", chip->fd); + + close(chip->fd); +} + +static int xGpioHandleRequest( + lgChipObj_p chip, struct gpiohandle_request *req) +{ + int i, gpio, mode; + int status; + uint32_t *offsets; + uint8_t *values; + + LG_DBG(LG_DEBUG_USER, "chip=*%p req=*%p", (void*)chip, (void*)req); + + LG_DBG(LG_DEBUG_ALLOC, "request %d with flags %d: GPIO=[%s]", + req->lines, req->flags, lgDbgInt2Str(req->lines, + (int *)req->lineoffsets)); + + status = ioctl(chip->fd, GPIO_GET_LINEHANDLE_IOCTL, req); + + if (status == 0) + { + offsets = calloc(req->lines, sizeof(uint32_t)); + + // struct needs GPIOHANDLES_MAX entries + values = calloc(GPIOHANDLES_MAX, sizeof(uint8_t)); + + if ((offsets == NULL) || (values == NULL)) + { + free(offsets); // passing NULL is legal + free(values); // passing NULL is legal + close(req->fd); + return LG_NOT_ENOUGH_MEMORY; + } + + LG_DBG(LG_DEBUG_ALLOC, "alloc offsets: *%p, values: *%p", + (void*)offsets, (void*)values); + + mode = 0; + + if (req->flags & GPIOHANDLE_REQUEST_INPUT) mode |= LG_CHIP_BIT_INPUT; + if (req->flags & GPIOHANDLE_REQUEST_OUTPUT) mode |= LG_CHIP_BIT_OUTPUT; + if (req->lines > 1) mode |= LG_CHIP_BIT_GROUP; + + for (i=0; ilines; i++) + { + gpio = req->lineoffsets[i]; + + chip->LineInf[gpio].mode = mode; + chip->LineInf[gpio].lFlags = req->flags; + chip->LineInf[gpio].group_size = req->lines; + chip->LineInf[gpio].fd = req->fd; + + chip->LineInf[gpio].offset = i; + + chip->LineInf[gpio].offsets = offsets; + chip->LineInf[gpio].values = values; + + offsets[i] = gpio; + values[i] = req->default_values[i]; + } + } + else + { + if (errno == EBUSY) status = LG_GPIO_BUSY; + else + { + fprintf(stderr, "*** error %d (%s) ***\n", errno, strerror(errno)); + status = LG_UNEGPECTED_ERROR; + } + } + return status; +} + +static int xClaim( + lgChipObj_p chip, + int lFlags, + int size, + const int *gpios, + const int *values) +{ + int i; + + struct gpiohandle_request req; + + LG_DBG(LG_DEBUG_USER, "chip=*%p size=%d gpios=[%s] values=[%s] lFlags=%x", + chip, size, lgDbgInt2Str(size, (int*)gpios), + lgDbgInt2Str(size, (int*)values), lFlags); + + if (size && (size <= GPIOHANDLES_MAX)) + { + for (i=0; i chip->lines) || + (chip->LineInf[gpios[i]].banned)) return LG_NOT_PERMITTED; + + req.lineoffsets[i] = gpios[i]; + + if (values != NULL) req.default_values[i] = values[i]; + else req.default_values[i] = 0; + } + + req.flags = lFlags; + + strncpy(req.consumer_label, chip->userLabel, + sizeof(req.consumer_label)); + req.lines = size; + + return xGpioHandleRequest(chip, &req); + } + else return LG_BAD_GROUP_SIZE; +} + +// implement public API + +static int xSetAsFree(lgChipObj_p chip, int gpio) +{ + lgLineInf_p GPIO; + int i, g; + lgTxRec_p pTx; + lgAlertRec_p pEvt; + + LG_DBG(LG_DEBUG_TRACE, "chip=*%p gpio=%d", (void*)chip, gpio); + + if (gpio >= chip->lines) return LG_BAD_GPIO_NUMBER; + + GPIO = &chip->LineInf[gpio]; + + if (GPIO->mode == LG_CHIP_MODE_UNKNOWN) + { + LG_DBG(LG_DEBUG_ALLOC, + "free unallocated GPIO: %d (mode %d)", gpio, GPIO->mode); + + return LG_OKAY; + } + + if (GPIO->mode & LG_CHIP_BIT_ALERT) + { + LG_DBG(LG_DEBUG_ALLOC, + "free alert GPIO: %d (mode %d)", gpio, GPIO->mode); + + if ((pEvt = lgGpioGetAlertRec(chip, gpio)) != NULL) + pEvt->active = 0; + + for (i=0; i<10; i++) + { + LG_DBG(LG_DEBUG_ALLOC, "waiting for inactive: %d", gpio); + + usleep(200); + + if ((pEvt = lgGpioGetAlertRec(chip, gpio)) == NULL) break; + } + + close(GPIO->fd); + + GPIO->mode = LG_CHIP_MODE_UNKNOWN; + + return LG_OKAY; + } + + // legal as long as group leader (a singleton is always a leader) + + if (gpio == GPIO->offsets[0]) + { + LG_DBG(LG_DEBUG_ALLOC, + "group free GPIO: %d (mode %d)", gpio, GPIO->mode); + + for (i=0; igroup_size; i++) + { + g = GPIO->offsets[i]; + + lgPthTxLock(); + + if ((pTx = lgGpioGetTxRec(chip, g, LG_TX_PWM)) != NULL) + { + pTx->active = 0; + LG_DBG(LG_DEBUG_ALLOC, "set PWM inactive: %d", gpio); + } + + if ((pTx = lgGpioGetTxRec(chip, g, LG_TX_WAVE)) != NULL) + { + pTx->active = 0; + LG_DBG(LG_DEBUG_ALLOC, "set PWM inactive: %d", gpio); + } + + lgPthTxUnlock(); + + chip->LineInf[g].mode = LG_CHIP_MODE_UNKNOWN; + + LG_DBG(LG_DEBUG_ALLOC, "set unused: %d", g); + } + + LG_DBG(LG_DEBUG_ALLOC, "close fd: %d", GPIO->fd); + + close(GPIO->fd); + + LG_DBG(LG_DEBUG_ALLOC, "free offsets: *%p, values: *%p", + (void*)GPIO->offsets, (void*)GPIO->values); + + free(GPIO->offsets); + free(GPIO->values); + + return LG_OKAY; + } + + return LG_NOT_GROUP_LEADER; +} + +static int xSetAsOutput( + lgChipObj_p chip, int lFlags, int size, + const int *gpios, const int *values) +{ + int mode; + + LG_DBG(LG_DEBUG_TRACE, "chip=*%p gpio=%d", (void*)chip, gpios[0]); + + lFlags &= ~(GPIOHANDLE_REQUEST_INPUT); + lFlags |= GPIOHANDLE_REQUEST_OUTPUT; + + if (size == 1) + { + /* single GPIO */ + + mode = chip->LineInf[gpios[0]].mode; + + if (mode & LG_CHIP_BIT_OUTPUT) + { + /* nothing to do, already an output */ + return LG_OKAY; + } + else + { + if (!(mode & LG_CHIP_BIT_GROUP)) + { + /* do auto free if singleton */ + LG_DBG(LG_DEBUG_ALLOC, "set as output auto free %d", gpios[0]); + xSetAsFree(chip, gpios[0]); + } + + return xClaim(chip, lFlags, size, gpios, values); + } + } + else + { + return xClaim(chip, lFlags, size, gpios, values); + } +} + +static int xSetAsInput( + lgChipObj_p chip, int lFlags, int size, const int *gpios) +{ + int mode; + + LG_DBG(LG_DEBUG_TRACE, "chip=*%p gpio=%d", (void*)chip, gpios[0]); + + lFlags &= ~(GPIOHANDLE_REQUEST_OUTPUT); + lFlags |= GPIOHANDLE_REQUEST_INPUT; + + if (size == 1) + { + /* single GPIO */ + + mode = chip->LineInf[gpios[0]].mode; + + if (mode & LG_CHIP_BIT_INPUT) + { + /* nothing to do, already an input */ + return LG_OKAY; + } + else + { + if (!(mode & LG_CHIP_BIT_GROUP)) + { + /* do auto free if singleton */ + LG_DBG(LG_DEBUG_ALLOC, "set as input auto free %d", gpios[0]); + xSetAsFree(chip, gpios[0]); + } + + return xClaim(chip, lFlags, size, gpios, NULL); + } + } + else + { + return xClaim(chip, lFlags, size, gpios, NULL); + } +} + +static int xSetAsPwm( + lgChipObj_p chip, + int gpio, + int micros_on, + int micros_off, + int micros_offset, + int cycles) +{ + lgLineInf_p GPIO; + lgTxRec_p p; + int zero = 0; + int status = 0; + + LG_DBG(LG_DEBUG_TRACE, "chip=*%p gpio=%d", (void*)chip, gpio); + + GPIO = &chip->LineInf[gpio]; + + /* do we need to change the mode? */ + + if (GPIO->mode == LG_CHIP_MODE_UNKNOWN) + { + xSetAsOutput( + chip, GPIOHANDLE_REQUEST_OUTPUT, 1, &gpio, &zero); + } + else + { + if (!(GPIO->mode & LG_CHIP_BIT_OUTPUT)) + { + /* not an output */ + if (!(GPIO->mode & LG_CHIP_BIT_GROUP)) + { + /* is a singleton */ + xSetAsOutput( + chip, GPIOHANDLE_REQUEST_OUTPUT, 1, &gpio, &zero); + } + } + } + + /* return if in wrong mode */ + + if (!(GPIO->mode & LG_CHIP_BIT_OUTPUT)) return LG_GPIO_NOT_AN_OUTPUT; + + lgPthTxLock(); + + if (((p = lgGpioGetTxRec(chip, gpio, LG_TX_PWM)) != NULL) && p->active) + { + if (micros_on || micros_off) + { + if ((micros_on + micros_off) > lgMinTxDelay) + { + /* delete prior pending entry if it has infinite cycles */ + + if ((p->entries > 1) && (p->cycles[p->entries-1] == -1)) + { + --p->entries; + } + + if (p->entries < LG_TX_BUF) + { + p->micros_on[p->entries] = micros_on; + p->micros_off[p->entries] = micros_off; + if (cycles) p->cycles[p->entries] = cycles; + else p->cycles[p->entries] = -1; + p->entries++; + status = LG_TX_BUF - p->entries; + } + else status = LG_TX_QUEUE_FULL; + } + else status = LG_BAD_PWM_MICROS; + } + else + { + p->active = 0; + } + + lgPthTxUnlock(); + } + else + { + lgPthTxUnlock(); + + if ((micros_on + micros_off) > lgMinTxDelay) + { + lgGpioCreateTxRec( + chip, gpio, micros_on, micros_off, micros_offset, cycles); + status = LG_TX_BUF - 1; + } + else return LG_BAD_PWM_MICROS; + } + + return status; +} + +static int xWave( + lgChipObj_p chip, int gpio, int count, lgPulse_p pulses) +{ + lgLineInf_p GPIO; + lgTxRec_p p; + int i; + int zero = 0; + int status = 0; + lgPulse_p pulsesTmp; + + LG_DBG(LG_DEBUG_TRACE, "chip=*%p gpio=%d", (void*)chip, gpio); + + GPIO = &chip->LineInf[gpio]; + + if (!(GPIO->mode & LG_CHIP_BIT_OUTPUT)) + { + /* not an output */ + if (!(GPIO->mode & LG_CHIP_BIT_GROUP)) + { + /* auto set output if a singleton */ + xSetAsOutput( + chip, GPIOHANDLE_REQUEST_OUTPUT, 1, &gpio, &zero); + } + } + + if (!(GPIO->mode & LG_CHIP_BIT_OUTPUT)) return LG_GPIO_NOT_AN_OUTPUT; + + pulsesTmp = malloc(count * sizeof(lgPulse_t)); + + if (!pulsesTmp) return LG_NO_MEMORY; + + for (i=0; iactive) + { + if (p->entries < LG_TX_BUF) + { + p->pulses[p->entries] = pulsesTmp; + p->num_pulses[p->entries] = count; + p->entries++; + status = LG_TX_BUF - p->entries; + } + else status = LG_TX_QUEUE_FULL; + + lgPthTxUnlock(); + } + else + { + lgPthTxUnlock(); + + lgGroupCreateWaveRec(chip, gpio, count, pulsesTmp); + status = LG_TX_BUF - 1; + } + + return status; +} + +void xWrite(lgChipObj_p chip, int gpio, int value) +{ + lgLineInf_p GPIO; + + GPIO = &chip->LineInf[gpio]; + GPIO->values[GPIO->offset]=value; + ioctl(GPIO->fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, GPIO->values); +} + +void xGroupWrite( + lgChipObj_p chip, int gpio, uint64_t groupBits, uint64_t groupMask) +{ + int i; + lgLineInf_p GPIO; + + GPIO = &chip->LineInf[gpio]; + + for (i=0; igroup_size; i++) + { + if (groupMask & (1<values[i] = 1; + else GPIO->values[i] = 0; + } + } + + ioctl(GPIO->fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, GPIO->values); +} + +// public API + +int lgGpiochipOpen(int gpioDev) +{ + int fd; + int handle; + lgChipObj_p chip; + struct gpiochip_info info; + char chipName[128]; + + LG_DBG(LG_DEBUG_TRACE, "gpioDev=%d", gpioDev); + + if (gpioDev < 0) + PARAM_ERROR(LG_BAD_GPIOCHIP, "bad gpioDev (%d)", gpioDev); + + sprintf(chipName, "/dev/gpiochip%d", gpioDev); + + fd = open(chipName, O_RDWR | O_CLOEXEC); + + if (fd < 0) + PARAM_ERROR(LG_CANNOT_OPEN_CHIP, "can't open gpiochip (%s)", chipName); + + if (ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &info)) + { + close(fd); + PARAM_ERROR(LG_NOT_A_GPIOCHIP, "ioct failed (%s)", chipName); + } + + handle = lgHdlAlloc( + LG_HDL_TYPE_GPIO, sizeof(lgChipObj_t), (void**)&chip, _lgGpiochipClose); + + if (handle < 0) + { + close(fd); + return LG_NOT_ENOUGH_MEMORY; + } + + chip->gpiochip = gpioDev; + + chip->handle = handle; + + /* calloc will zero all members */ + chip->LineInf = calloc(info.lines, sizeof(lgLineInf_t)); + + if (chip->LineInf == NULL) + { + lgHdlFree(handle, LG_HDL_TYPE_GPIO); + ALLOC_ERROR(LG_NOT_ENOUGH_MEMORY, "can't allocate gpio lines"); + } + + LG_DBG(LG_DEBUG_ALLOC, "alloc LineInf: *%p", (void*)chip->LineInf); + + strncpy(chip->name, info.name, sizeof(chip->name)); + strncpy(chip->label, info.label, sizeof(chip->label)); + + chip->lines = info.lines; + + chip->fd = fd; + + strncpy(chip->userLabel, "lg", sizeof(chip->userLabel)); + + lgPthTxStart(); + + lgPthAlertStart(); + + return handle; +} + +int lgGpiochipClose(int handle) +{ + int status; + lgChipObj_p chip; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d", handle); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_GPIO, (void **)&chip); + + if (status == LG_OKAY) + { + status = lgHdlFree(handle, LG_HDL_TYPE_GPIO); + + lgHdlUnlock(handle); + } + + return status; +} + +int lgGpioSetBannedState(int handle, int gpio, int banned) +{ + int status; + lgLineInf_p GPIO; + lgChipObj_p chip; + + LG_DBG(LG_DEBUG_TRACE, + "handle=%d gpio=%d banned=%d", handle, gpio, banned); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_GPIO, (void **)&chip); + + if (status == LG_OKAY) + { + if (gpio < chip->lines) + { + GPIO = &chip->LineInf[gpio]; + GPIO->banned = banned; + } + else status = LG_BAD_GPIO_NUMBER; + + lgHdlUnlock(handle); + } + + return status; +} + + + +int lgGpioGetChipInfo(int handle, lgChipInfo_p chipInfo) +{ + int status; + struct gpiochip_info cinfo; + lgChipObj_p chip; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d chipInfo=*%p", handle, (void*)chipInfo); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_GPIO, (void **)&chip); + + if (status == LG_OKAY) + { + status = ioctl(chip->fd, GPIO_GET_CHIPINFO_IOCTL, &cinfo); + + if (status == 0) + { + chipInfo->lines = cinfo.lines; + strncpy(chipInfo->name, cinfo.name, sizeof(chipInfo->name)); + strncpy(chipInfo->label, cinfo.label, sizeof(chipInfo->label)); + } + else status = LG_BAD_CHIPINFO_IOCTL; + + lgHdlUnlock(handle); + } + + return status; +} + +int lgGpioGetLineInfo(int handle, int gpio, lgLineInfo_p lineInfo) +{ + int status; + struct gpioline_info linfo; + lgChipObj_p chip; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d gpio=%d lineInfo=*%p", + handle, gpio, (void*)lineInfo); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_GPIO, (void **)&chip); + + if (status == LG_OKAY) + { + if (gpio < chip->lines) + { + linfo.line_offset = gpio; + + status = ioctl(chip->fd, GPIO_GET_LINEINFO_IOCTL, &linfo); + + if (status == 0) + { + lineInfo->offset = linfo.line_offset; + lineInfo->lFlags = linfo.flags; + strncpy(lineInfo->name, linfo.name, sizeof(lineInfo->name)); + strncpy(lineInfo->user, linfo.consumer, sizeof(lineInfo->user)); + } + else status = LG_BAD_LINEINFO_IOCTL; + } + else status = LG_BAD_GPIO_NUMBER; + + lgHdlUnlock(handle); + } + + return status; +} + +int lgGpioGetMode(int handle, int gpio) +{ + int status; + struct gpioline_info linfo; + lgChipObj_p chip; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d gpio=%d", handle, gpio); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_GPIO, (void **)&chip); + + if (status == LG_OKAY) + { + if (gpio < chip->lines) + { + linfo.line_offset = gpio; + + status = ioctl(chip->fd, GPIO_GET_LINEINFO_IOCTL, &linfo); + + if (status == 0) + { + status = (linfo.flags & 0xff) | (chip->LineInf[gpio].mode << 8); + } + else status = LG_BAD_LINEINFO_IOCTL; + } + else status = LG_BAD_GPIO_NUMBER; + + lgHdlUnlock(handle); + } + + return status; +} + +int lgGroupFree(int handle, int gpio) +{ + int status; + lgChipObj_p chip; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d gpio=%d", handle, gpio); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_GPIO, (void **)&chip); + + if (status == LG_OKAY) + { + if (gpio < chip->lines) + { + if (chip->LineInf[gpio].mode != LG_CHIP_MODE_UNKNOWN) + { + if (chip->LineInf[gpio].offset == 0) + { + status = xSetAsFree(chip, gpio); + } + else status = LG_NOT_GROUP_LEADER; + } + else status = LG_GPIO_NOT_ALLOCATED; + } + else status = LG_BAD_GPIO_NUMBER; + + lgHdlUnlock(handle); + } + + return status; +} + +int lgGpioFree(int handle, int gpio) +{ + int status; + lgChipObj_p chip; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d gpio=%d", handle, gpio); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_GPIO, (void **)&chip); + + if (status == LG_OKAY) + { + if (gpio < chip->lines) + { + if (chip->LineInf[gpio].mode != LG_CHIP_MODE_UNKNOWN) + { + if (chip->LineInf[gpio].offset == 0) + { + status = xSetAsFree(chip, gpio); + } + else status = LG_NOT_GROUP_LEADER; + } + else status = LG_GPIO_NOT_ALLOCATED; + } + else status = LG_BAD_GPIO_NUMBER; + + lgHdlUnlock(handle); + } + + return status; +} + +int lgGpioSetUser(int handle, const char *user) +{ + int status; + lgChipObj_p chip; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d user=%s", handle, user); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_GPIO, (void **)&chip); + + if (status == LG_OKAY) + { + strncpy(chip->userLabel, user, sizeof(chip->userLabel)-1); + + lgHdlUnlock(handle); + } + + return status; +} + + +int lgGroupClaimInput( + int handle, int lFlags, int size, const int *gpios) +{ + int status; + lgChipObj_p chip; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d lFlags=%x size=%d gpios=[%s]", + handle, lFlags, size, lgDbgInt2Str(size, (int*)gpios)); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_GPIO, (void **)&chip); + + if (status == LG_OKAY) + { + status = xSetAsInput(chip, lFlags, size, gpios); + + lgHdlUnlock(handle); + } + + return status; +} + +int lgGpioClaimInput(int handle, int lFlags, int gpio) +{ + int gpios[]={gpio}; + + LG_DBG(LG_DEBUG_TRACE, + "handle=%d lFlags=%x gpio=%d", handle, lFlags, gpio); + + return lgGroupClaimInput(handle, lFlags, 1, gpios); +} + + +int lgGroupClaimOutput( + int handle, int lFlags, int size, const int *gpios, const int *values) +{ + lgChipObj_p chip; + int status; + + LG_DBG(LG_DEBUG_TRACE, + "handle=%d lFlags=%x size=%d gpios=[%s] values=[%s]", + handle, lFlags, size, lgDbgInt2Str(size, (int*)gpios), + lgDbgInt2Str(size, (int*)values)); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_GPIO, (void **)&chip); + + if (status == LG_OKAY) + { + status = xSetAsOutput(chip, lFlags, size, gpios, values); + + lgHdlUnlock(handle); + } + + return status; +} + +int lgGpioClaimOutput(int handle, int lFlags, int gpio, int value) +{ + int gpios[]={gpio}; + int values[]={value}; + + LG_DBG(LG_DEBUG_TRACE, + "handle=%d lFlags=%x gpio=%d value=%d", + handle, lFlags, gpio, value); + + return lgGroupClaimOutput(handle, lFlags, 1, gpios, values); +} + +int lgGpioClaimAlert( + int handle, int lFlags, int eFlags, int gpio, int nfyHandle) +{ + lgChipObj_p chip; + int status; + int mode; + lgAlertRec_p p; + struct gpioevent_request req; + uint32_t *offsets; + uint8_t *values; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d lFlags=%x eFlags=%x gpio=%d nfyHandle=%d", + handle, lFlags, eFlags, gpio, nfyHandle); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_GPIO, (void **)&chip); + + if (status == LG_OKAY) + { + if (gpio < chip->lines) + { + mode = chip->LineInf[gpio].mode; + + if (!(mode & LG_CHIP_BIT_GROUP)) + { + LG_DBG(LG_DEBUG_ALLOC, "set as alert auto free %d", gpio); + + xSetAsFree(chip, gpio); + + lFlags &= ~(GPIOHANDLE_REQUEST_OUTPUT); + lFlags |= GPIOHANDLE_REQUEST_INPUT; + + req.lineoffset = gpio; + req.handleflags = lFlags; + req.eventflags = eFlags; + strncpy(req.consumer_label, chip->userLabel, + sizeof(req.consumer_label)); + + status = ioctl(chip->fd, GPIO_GET_LINEEVENT_IOCTL, &req); + + if (status == 0) + { + offsets = calloc(1, sizeof(uint32_t)); + + // struct needs GPIOHANDLES_MAX entries + values = calloc(GPIOHANDLES_MAX, sizeof(uint8_t)); + + if ((offsets == NULL) || (values == NULL)) + { + free(offsets); // passing NULL is legal + free(values); // passing NULL is legal + close(req.fd); + return LG_NOT_ENOUGH_MEMORY; + } + + chip->LineInf[gpio].mode = LG_CHIP_BIT_ALERT; + chip->LineInf[gpio].lFlags = lFlags; + chip->LineInf[gpio].eFlags = eFlags; + chip->LineInf[gpio].group_size = 1; + chip->LineInf[gpio].fd = req.fd; + chip->LineInf[gpio].offset = 0; + chip->LineInf[gpio].offsets = offsets; + chip->LineInf[gpio].values = values; + + if ((p = lgGpioGetAlertRec(chip, gpio)) != NULL) + p->active= 0; + + lgGpioCreateAlertRec( + chip, gpio, &chip->LineInf[gpio], nfyHandle); + } + else status = LG_BAD_EVENT_REQUEST; + } + else status = LG_INVALID_GROUP_ALERT; + } + else status = LG_BAD_GPIO_NUMBER; + + lgHdlUnlock(handle); + } + + return status; +} + +int lgTxPulse( + int handle, + int gpio, + int micros_on, + int micros_off, + int micros_offset, + int cycles) +{ + lgChipObj_p chip; + int status; + + LG_DBG(LG_DEBUG_TRACE, + "handle=%d gpio=%d on=%d off=%d offset=%d cycles=%d", + handle, gpio, micros_on, micros_off, micros_offset, cycles); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_GPIO, (void **)&chip); + + if (status == LG_OKAY) + { + if (gpio < chip->lines) + { + status = xSetAsPwm( + chip, gpio, micros_on, micros_off, micros_offset, cycles); + } + else status = LG_BAD_GPIO_NUMBER; + + lgHdlUnlock(handle); + } + + return status; +} + +int lgTxWave(int handle, int gpio, int count, lgPulse_p pulses) +{ + lgChipObj_p chip; + int status; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d gpio=%d count=%d", handle, gpio, count); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_GPIO, (void **)&chip); + + if (status == LG_OKAY) + { + if (gpio < chip->lines) + { + status = xWave(chip, gpio, count, pulses); + } + else status = LG_BAD_GPIO_NUMBER; + + lgHdlUnlock(handle); + } + + return status; +} + +int lgTxBusy(int handle, int gpio, int kind) +{ + lgChipObj_p chip; + lgTxRec_p p; + int status; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d gpio=%d kind=%d", handle, gpio, kind); + + if ((kind != LG_TX_PWM) && (kind != LG_TX_WAVE)) + PARAM_ERROR(LG_BAD_TX_TYPE, "bad tx kind (%d)", kind); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_GPIO, (void **)&chip); + + if (status == LG_OKAY) + { + if (gpio < chip->lines) + { + lgPthTxLock(); + + if (((p = lgGpioGetTxRec(chip, gpio, kind)) != NULL) && p->active) + status = 1; + + lgPthTxUnlock(); + } + else status = LG_BAD_GPIO_NUMBER; + + lgHdlUnlock(handle); + } + + return status; +} + +int lgTxRoom(int handle, int gpio, int kind) +{ + lgChipObj_p chip; + lgTxRec_p p; + int status; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d gpio=%d kind=%d", handle, gpio, kind); + + if ((kind != LG_TX_PWM) && (kind != LG_TX_WAVE)) + PARAM_ERROR(LG_BAD_TX_TYPE, "bad tx kind (%d)", kind); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_GPIO, (void **)&chip); + + if (status == LG_OKAY) + { + if (gpio < chip->lines) + { + lgPthTxLock(); + + if (((p = lgGpioGetTxRec(chip, gpio, kind)) != NULL) && p->active) + status = LG_TX_BUF - p->entries; + else + status = LG_TX_BUF; + + lgPthTxUnlock(); + } + else status = LG_BAD_GPIO_NUMBER; + + lgHdlUnlock(handle); + } + + return status; +} + +int lgTxPwm( + int handle, + int gpio, + float pwmFrequency, + float pwmDutyCycle, + int pwmOffset, + int pwmCycles) +{ + int micros, micros_on, micros_off; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d gpio=%d freq=%f duty=%f", + handle, gpio, pwmFrequency, pwmDutyCycle); + + if (pwmFrequency == 0.0) return lgTxPulse(handle, gpio, 0, 0, 0, 0); + + if ((pwmFrequency < 0.1) || (pwmFrequency > 1e4)) + PARAM_ERROR(LG_BAD_PWM_FREQ, + "bad PWM frequency (%f)", pwmFrequency); + + if ((pwmDutyCycle < 0.0) || (pwmDutyCycle > 100.0)) + PARAM_ERROR(LG_BAD_PWM_DUTY, + "bad PWM duty cycle (%f)", pwmDutyCycle); + + micros = ((1.0e6 / pwmFrequency) + 0.5); + micros_on = ((pwmDutyCycle / 100.0 * micros) + 0.5); + micros_off = micros - micros_on; + + return lgTxPulse( + handle, gpio, micros_on, micros_off, pwmOffset, pwmCycles); +} + +int lgTxServo( + int handle, + int gpio, + int pulseWidth, + int servoFrequency, + int servoOffset, + int servoCycles) + +{ + int micros, micros_on, micros_off; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d gpio=%d freq=%d width=%d", + handle, gpio, servoFrequency, pulseWidth); + + if (pulseWidth == 0) return lgTxPulse(handle, gpio, 0, 0, 0, 0); + + if ((servoFrequency < 40) || (servoFrequency > 500)) + PARAM_ERROR(LG_BAD_SERVO_FREQ, + "bad servo frequency (%d)", servoFrequency); + + if ((pulseWidth < 500) || (pulseWidth > 2500)) + PARAM_ERROR(LG_BAD_SERVO_WIDTH, + "bad servo pulse width (%d)", pulseWidth); + + micros = ((1.0e6 / servoFrequency) + 0.5); + micros_on = pulseWidth; + micros_off = micros - micros_on; + + if (micros_off < 0) + PARAM_ERROR(LG_BAD_SERVO_WIDTH, + "bad servo pulse width (%d)", pulseWidth); + + return lgTxPulse( + handle, gpio, micros_on, micros_off, servoOffset, servoCycles); +} + +int lgGpioRead(int handle, int gpio) +{ + int status; + lgLineInf_p GPIO; + lgChipObj_p chip; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d gpio=%d", handle, gpio); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_GPIO, (void **)&chip); + + if (status == LG_OKAY) + { + if (gpio < chip->lines) + { + GPIO = &chip->LineInf[gpio]; + + if (GPIO->mode == LG_CHIP_MODE_UNKNOWN) + { + status = xSetAsInput(chip, 0, 1, &gpio); + } + + if (GPIO->mode != LG_CHIP_MODE_UNKNOWN) + { + status = ioctl( + GPIO->fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, GPIO->values); + + if (status == 0) + status = GPIO->values[GPIO->offset]; + else + status = LG_BAD_READ; + } + else status = LG_GPIO_NOT_ALLOCATED; + } + else status = LG_BAD_GPIO_NUMBER; + + lgHdlUnlock(handle); + } + + return status; +} + +int lgGpioWrite(int handle, int gpio, int value) +{ + int status; + lgLineInf_p GPIO; + lgChipObj_p chip; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d gpio=%d value=%d", handle, gpio, value); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_GPIO, (void **)&chip); + + if (status == LG_OKAY) + { + if (gpio < chip->lines) + { + GPIO = &chip->LineInf[gpio]; + + if (GPIO->mode & LG_CHIP_BIT_OUTPUT) + { + GPIO->values[GPIO->offset] = value; + + status = ioctl( + GPIO->fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, GPIO->values); + + if (status) status = LG_BAD_WRITE; + } + else + { + /* not an output */ + if (!(GPIO->mode & LG_CHIP_BIT_GROUP)) + { + /* auto set output if a singleton */ + status = xSetAsOutput(chip, 0, 1, &gpio, &value); + } + else status = LG_GPIO_NOT_AN_OUTPUT; + } + } + else status = LG_BAD_GPIO_NUMBER; + + lgHdlUnlock(handle); + } + + return status; +} + + +int lgGroupRead( + int handle, int gpio, uint64_t *bits) +{ + int i; + lgLineInf_p GPIO; + int status; + lgChipObj_p chip; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d gpio=%d bits=%"PRIx64"", + handle, gpio, *bits); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_GPIO, (void **)&chip); + + if (status == LG_OKAY) + { + if (gpio < chip->lines) + { + GPIO = &chip->LineInf[gpio]; + + if (GPIO->offset == 0) + { + if (GPIO->mode != LG_CHIP_MODE_UNKNOWN) + { + status = ioctl( + GPIO->fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, GPIO->values); + + if (status == 0) + { + *bits = 0; + + for (i=0; igroup_size; i++) + { + if (GPIO->values[i]) *bits |= ((uint64_t)1<group_size; + } + else status = LG_BAD_READ; + } + else status = LG_GPIO_NOT_ALLOCATED; + } + else status = LG_NOT_GROUP_LEADER; + } + else status = LG_BAD_GPIO_NUMBER; + + lgHdlUnlock(handle); + } + + return status; +} + +int lgGroupWrite( + int handle, int gpio, uint64_t groupBits, uint64_t groupMask) +{ + int status; + int i; + lgLineInf_p GPIO; + lgChipObj_p chip; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d gpio=%d bits=%"PRIx64" mask=%"PRIx64"", + handle, gpio, groupBits, groupMask); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_GPIO, (void **)&chip); + + if (status == LG_OKAY) + { + if (gpio < chip->lines) + { + GPIO = &chip->LineInf[gpio]; + + if (GPIO->offset == 0) + { + if (GPIO->mode != LG_CHIP_MODE_UNKNOWN) + { + for (i=0; igroup_size; i++) + { + if (groupMask & (1<values[i] = 1; + else GPIO->values[i] = 0; + } + } + + status = ioctl( + GPIO->fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, GPIO->values); + + if (status == 0) + { + status = GPIO->group_size; + } + else status = LG_BAD_WRITE; + } + else status = LG_GPIO_NOT_ALLOCATED; + } + else status = LG_NOT_GROUP_LEADER; + } + else status = LG_BAD_GPIO_NUMBER; + + lgHdlUnlock(handle); + } + + return status; +} + +int lgGpioSetDebounce(int handle, int gpio, int debounce_us) +{ + int status; + lgLineInf_p GPIO; + lgChipObj_p chip; + lgAlertRec_p p; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d gpio=%d debounce_us=%d", + handle, gpio, debounce_us); + + if ((debounce_us < 0) || (debounce_us > LG_MAX_MICS_DEBOUNCE)) + PARAM_ERROR(LG_BAD_DEBOUNCE_MICS, "bad debounce (%d)", debounce_us); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_GPIO, (void **)&chip); + + if (status == LG_OKAY) + { + if (gpio < chip->lines) + { + GPIO = &chip->LineInf[gpio]; + + GPIO->debounce_us = debounce_us; + + if ((p = lgGpioGetAlertRec(chip, gpio)) != NULL) + p->debounce_nanos = debounce_us * 1e3; + } + else status = LG_BAD_GPIO_NUMBER; + + lgHdlUnlock(handle); + } + + return status; +} + +int lgGpioSetWatchdog(int handle, int gpio, int watchdog_us) +{ + int status; + lgLineInf_p GPIO; + lgChipObj_p chip; + lgAlertRec_p p; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d gpio=%d watchdog_us=%d", + handle, gpio, watchdog_us); + + if ((watchdog_us < 0) || (watchdog_us > LG_MAX_MICS_WATCHDOG)) + PARAM_ERROR(LG_BAD_WATCHDOG_MICS, "bad watchdog (%d)", watchdog_us); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_GPIO, (void **)&chip); + + if (status == LG_OKAY) + { + if (gpio < chip->lines) + { + GPIO = &chip->LineInf[gpio]; + + GPIO->watchdog_us = watchdog_us; + + if ((p = lgGpioGetAlertRec(chip, gpio)) != NULL) + p->watchdog_nanos = watchdog_us * 1e3; + } + else status = LG_BAD_GPIO_NUMBER; + + lgHdlUnlock(handle); + } + + return status; +} + +int lgGpioSetAlertsFunc( + int handle, int gpio, lgGpioAlertsFunc_t cbf, void *userdata) +{ + lgLineInf_p GPIO; + lgChipObj_p chip; + int status; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d gpio=%d func=*%p userdata=*%p", + handle, gpio, cbf, userdata); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_GPIO, (void **)&chip); + + if (status == LG_OKAY) + { + if (gpio < chip->lines) + { + GPIO = &chip->LineInf[gpio]; + GPIO->alertFunc = cbf; + GPIO->userdata = userdata; + } + else status = LG_BAD_GPIO_NUMBER; + + lgHdlUnlock(handle); + } + + return status; +} + +void lgGpioSetSamplesFunc(lgGpioAlertsFunc_t cbf, void *userdata) +{ + LG_DBG(LG_DEBUG_TRACE, "func=*%p userdata=*%p", cbf, userdata); + + lgGpioSamplesFunc = cbf; + lgGpioSamplesUserdata = userdata; +} + diff --git a/lgGpio.h b/lgGpio.h new file mode 100644 index 0000000..a19e393 --- /dev/null +++ b/lgGpio.h @@ -0,0 +1,70 @@ +/* +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 +*/ + +#ifndef _LG_GPIO_H +#define _LG_GPIO_H + +#include "lgpio.h" + +typedef struct lgLineInf_s +{ + int banned; + int mode; + int lFlags; + int eFlags; + int group_size; + int fd; + int debounce_us; + int watchdog_us; + callbk_t alertFunc; + void *userdata; + uint32_t offset; + uint32_t *offsets; + uint8_t *values; +} lgLineInf_t, *lgLineInf_p; + +typedef struct lgChipObj_s +{ + int gpiochip; + int handle; /* needed for auto resource free */ + uint32_t lines; + int fd; + lgLineInf_p LineInf; + char name[LG_GPIO_NAME_LEN]; + char label[LG_GPIO_LABEL_LEN]; + char userLabel[LG_GPIO_USER_LEN]; +} lgChipObj_t, *lgChipObj_p; + +void xWrite(lgChipObj_p chip, int gpio, int value); +void xGroupWrite( + lgChipObj_p chip, int gpio, uint64_t groupBits, uint64_t groupMask); + +extern callbk_t lgGpioSamplesFunc; +extern void *lgGpioSamplesUserdata; + +#endif + diff --git a/lgHdl.c b/lgHdl.c new file mode 100644 index 0000000..34e0027 --- /dev/null +++ b/lgHdl.c @@ -0,0 +1,440 @@ +/* +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 +*/ + +#include +#include +#include + +#include "lgpio.h" + +#include "lgCtx.h" +#include "lgDbg.h" +#include "lgHdl.h" + + +#define LG_HDL_FREE 0 +#define LG_HDL_RSVD 1 + +#define LG_HDL_SLOTS 1024 + +typedef struct +{ + uint32_t magic; + int32_t first; + int32_t last; +} slgHdlTypeUsage_t; + +typedef struct +{ + char user[LG_USER_LEN]; // creator (defines permissions) + void *obj; // pointer to object + pthread_mutex_t mutex; // access control + int type; // type of object, e.g. GPIO, file, etc. + int next; // next handle of type + int previous; // previous handle of type + uint32_t magic; // guard to check object of correct type + callbk_t destructor; // used to correctly free object resources + int owner; // id of owning thread + int share; // if object can be used by non-owners +} lgHdlHdr_t, *lgHdlHdr_p; + +typedef struct +{ + lgHdlHdr_p header; + pthread_mutex_t mutex; // access control +} lgHdl_t; + +static pthread_mutex_t slgHdlMutex = PTHREAD_MUTEX_INITIALIZER; + +lgHdl_t lgHdl[LG_HDL_SLOTS]; + +static slgHdlTypeUsage_t slgHdlTypeUsage[]= +{ + {101442315, -1, -1}, {263997524, -1, -1}, {354388063, -1, -1}, + {412249733, -1, -1}, {503487673, -1, -1}, {641332553, -1, -1}, + {707085925, -1, -1}, {806156471, -1, -1}, {979542324, -1, -1}, + {128752466, -1, -1}, {271619210, -1, -1}, {380943518, -1, -1}, + {482972576, -1, -1}, {549545782, -1, -1}, {651826746, -1, -1}, + {724133862, -1, -1}, {894093461, -1, -1}, {967000257, -1, -1}, +}; + +static pthread_once_t xInited = PTHREAD_ONCE_INIT; + +static void xInit(void) +{ + int i; + for (i=0; i= 0) + { + // add handle to end of chain for type + h->previous = last; + h->next = -1; + lgHdl[last].header->next = handle; + slgHdlTypeUsage[type].last = handle; + } + else + { + h->previous = -1; + h->next = -1; + slgHdlTypeUsage[type].first = handle; + slgHdlTypeUsage[type].last = handle; + } + + h->magic = slgHdlTypeUsage[type].magic; + h->destructor = destructor; + h->obj = *objPtr; + h->type = type; + + h->share = Ctx->autoSetShare; + h->owner = Ctx->owner; + strncpy(h->user, Ctx->user, LG_USER_LEN); + + lgHdl[handle].header = h; + + return handle; +} + +int lgHdlLock(int handle) +{ + pthread_once(&xInited, xInit); + + if ((handle < 0) || (handle >= LG_HDL_SLOTS)) + PARAM_ERROR(LG_BAD_HANDLE, "bad handle (%d)", handle); + + pthread_mutex_lock(&lgHdl[handle].mutex); + + return LG_OKAY; +} + +int lgHdlUnlock(int handle) +{ + pthread_once(&xInited, xInit); + + if ((handle < 0) || (handle >= LG_HDL_SLOTS)) + PARAM_ERROR(LG_BAD_HANDLE, "bad handle (%d)", handle); + + pthread_mutex_unlock(&lgHdl[handle].mutex); + + return LG_OKAY; +} + +int lgHdlGetObj(int handle, int type, void **objPtr) +{ + lgHdlHdr_p h; + + pthread_once(&xInited, xInit); + + if ((handle < 0) || (handle >= LG_HDL_SLOTS)) + PARAM_ERROR(LG_BAD_HANDLE, "bad handle (%d)", handle); + + h = lgHdl[handle].header; + + if ((h == (void *)LG_HDL_FREE) || (h == (void *)LG_HDL_RSVD)) + PARAM_ERROR(LG_BAD_HANDLE, "bad handle (%d)", handle); + + if ((h->type != type) || (h->magic != slgHdlTypeUsage[type].magic)) + PARAM_ERROR(LG_BAD_HANDLE, "bad handle (%d)", handle); + + *objPtr = h->obj; + + return LG_OKAY; +} + +int lgHdlGetLockedObj(int handle, int type, void **objPtr) +{ + lgHdlHdr_p h; + lgCtx_p Ctx; + + pthread_once(&xInited, xInit); + + Ctx = lgCtxGet(); + + if ((handle < 0) || (handle >= LG_HDL_SLOTS)) + PARAM_ERROR(LG_BAD_HANDLE, "bad handle (%d)", handle); + + pthread_mutex_lock(&lgHdl[handle].mutex); + + h = lgHdl[handle].header; + + if ((h == (void *)LG_HDL_FREE) || (h == (void *)LG_HDL_RSVD)) + { + pthread_mutex_unlock(&lgHdl[handle].mutex); + PARAM_ERROR(LG_BAD_HANDLE, "bad handle (%d)", handle); + } + + if ((h->type != type) || (h->magic != slgHdlTypeUsage[type].magic)) + { + pthread_mutex_unlock(&lgHdl[handle].mutex); + PARAM_ERROR(LG_BAD_HANDLE, "bad handle (%d)", handle); + } + + if ((h->owner != Ctx->owner) && + ((h->share == 0) || + (h->share != Ctx->autoUseShare) || + (strcmp(h->user, Ctx->user) != 0))) + { + pthread_mutex_unlock(&lgHdl[handle].mutex); + PARAM_ERROR(LG_NO_PERMISSIONS, + "not owned or shared by user (%d)", handle); + } + + *objPtr = h->obj; + + return LG_OKAY; +} + +int lgHdlGetLockedObjTrusted(int handle, int type, void **objPtr) +{ + lgHdlHdr_p h; + + pthread_once(&xInited, xInit); + + pthread_mutex_lock(&lgHdl[handle].mutex); + + h = lgHdl[handle].header; + + if ((h == (void *)LG_HDL_FREE) || (h == (void *)LG_HDL_RSVD)) + { + pthread_mutex_unlock(&lgHdl[handle].mutex); + PARAM_ERROR(LG_BAD_HANDLE, "bad handle (%d)", handle); + } + + if ((h->type != type) || (h->magic != slgHdlTypeUsage[type].magic)) + { + pthread_mutex_unlock(&lgHdl[handle].mutex); + PARAM_ERROR(LG_BAD_HANDLE, "bad handle (%d)", handle); + } + + *objPtr = h->obj; + + return LG_OKAY; +} + +int lgHdlSetShare(int handle, int share) +{ + lgHdlHdr_p h; + lgCtx_p Ctx; + + pthread_once(&xInited, xInit); + + Ctx = lgCtxGet(); + + if ((handle < 0) || (handle >= LG_HDL_SLOTS)) + PARAM_ERROR(LG_BAD_HANDLE, "bad handle (%d)", handle); + + pthread_mutex_lock(&lgHdl[handle].mutex); + + h = lgHdl[handle].header; + + if ((h == (void *)LG_HDL_FREE) || (h == (void *)LG_HDL_RSVD)) + { + pthread_mutex_unlock(&lgHdl[handle].mutex); + PARAM_ERROR(LG_BAD_HANDLE, "bad handle (%d)", handle); + } + + if (h->owner != Ctx->owner) + { + pthread_mutex_unlock(&lgHdl[handle].mutex); + PARAM_ERROR(LG_NO_PERMISSIONS, "not owned (%d)", handle); + } + + h->share = share; + + pthread_mutex_unlock(&lgHdl[handle].mutex); + + return LG_OKAY; +} + +int lgHdlGetHandlesForType(int type, int *handles, int size) +{ + int hdl; + int count=0; + + pthread_once(&xInited, xInit); + + pthread_mutex_lock(&slgHdlMutex); + + hdl = slgHdlTypeUsage[type].first; + + while (hdl >= 0) + { + if (count < size) handles[count] = hdl; + count ++; + hdl = lgHdl[hdl].header->next; + } + + pthread_mutex_unlock(&slgHdlMutex); + + return count; +} + +int lgHdlFree(int handle, int type) +{ + int status; + void **dummy; + lgHdlHdr_p h; + + pthread_once(&xInited, xInit); + + LG_DBG(LG_DEBUG_TRACE, "handle=%d type=%d", handle, type); + + pthread_mutex_lock(&slgHdlMutex); + + status = lgHdlGetObj(handle, type, (void **)&dummy); + + if (status == LG_OKAY) + { + h = lgHdl[handle].header; + + if (h->previous >= 0) + { + // not first + lgHdl[h->previous].header->next = h->next; + } + else + { + // first + slgHdlTypeUsage[type].first = h->next; + } + + if (h->next >= 0) + { + // not last + lgHdl[h->next].header->previous = h->previous; + } + else + { + slgHdlTypeUsage[type].last = h->previous; + } + + lgHdl[handle].header = NULL; + + if (h->destructor != NULL) (h->destructor)(h->obj); + + if (h->obj != NULL) free(h->obj); + + free(h); + } + pthread_mutex_unlock(&slgHdlMutex); + + return status; +} + +// purge all handles with a given owner +void lgHdlPurgeByOwner(int owner) +{ + int i; + lgHdlHdr_p h; + + pthread_once(&xInited, xInit); + + for (i=0; iowner == owner) && (!h->share)) + { + lgHdlFree(i, h->type); + } + } + } +} + diff --git a/lgHdl.h b/lgHdl.h new file mode 100644 index 0000000..32ab3cd --- /dev/null +++ b/lgHdl.h @@ -0,0 +1,66 @@ +/* +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 +*/ + +#ifndef LG_HDL_H +#define LG_HDL_H + +#include + +#include "lgpio.h" + +#define LG_HDL_TYPE_NONE 0 +#define LG_HDL_TYPE_GPIO 1 +#define LG_HDL_TYPE_I2C 2 +#define LG_HDL_TYPE_FILE 3 +#define LG_HDL_TYPE_SERIAL 4 +#define LG_HDL_TYPE_NOTIFY 5 +#define LG_HDL_TYPE_SCRIPT 6 +#define LG_HDL_TYPE_SPI 7 + +int lgHdlAlloc + (int type, int objSize, void **objPtr, callbk_t destructor); + +int lgHdlFree(int handle, int type); + +void lgHdlPurgeByOwner(int owner); + +int lgHdlGetObj(int handle, int type, void **objPtr); + +int lgHdlGetLockedObj(int handle, int type, void **objPtr); + +int lgHdlGetLockedObjTrusted(int handle, int type, void **objPtr); + +int lgHdlSetShare(int handle, int share); + +int lgHdlGetHandlesForType(int type, int *handles, int size); + +int lgHdlLock(int handle); + +int lgHdlUnlock(int handle); + +#endif + diff --git a/lgI2C.c b/lgI2C.c new file mode 100644 index 0000000..c1a9fcc --- /dev/null +++ b/lgI2C.c @@ -0,0 +1,1093 @@ +/* +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 +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include "lgpio.h" + +#include "lgDbg.h" +#include "lgHdl.h" + +#define LG_I2C_SLAVE 0x0703 +#define LG_I2C_FUNCS 0x0705 +#define LG_I2C_RDWR 0x0707 +#define LG_I2C_SMBUS 0x0720 + +#define LG_I2C_SMBUS_READ 1 +#define LG_I2C_SMBUS_WRITE 0 + +#define LG_I2C_SMBUS_QUICK 0 +#define LG_I2C_SMBUS_BYTE 1 +#define LG_I2C_SMBUS_BYTE_DATA 2 +#define LG_I2C_SMBUS_WORD_DATA 3 +#define LG_I2C_SMBUS_PROC_CALL 4 +#define LG_I2C_SMBUS_BLOCK_DATA 5 +#define LG_I2C_SMBUS_I2C_BLOCK_BROKEN 6 +#define LG_I2C_SMBUS_BLOCK_PROC_CALL 7 +#define LG_I2C_SMBUS_I2C_BLOCK_DATA 8 + +#define LG_I2C_SMBUS_BLOCK_MAX 32 +#define LG_I2C_SMBUS_I2C_BLOCK_MAX 32 + +#define LG_I2C_FUNC_SMBUS_QUICK 0x00010000 +#define LG_I2C_FUNC_SMBUS_READ_BYTE 0x00020000 +#define LG_I2C_FUNC_SMBUS_WRITE_BYTE 0x00040000 +#define LG_I2C_FUNC_SMBUS_READ_BYTE_DATA 0x00080000 +#define LG_I2C_FUNC_SMBUS_WRITE_BYTE_DATA 0x00100000 +#define LG_I2C_FUNC_SMBUS_READ_WORD_DATA 0x00200000 +#define LG_I2C_FUNC_SMBUS_WRITE_WORD_DATA 0x00400000 +#define LG_I2C_FUNC_SMBUS_PROC_CALL 0x00800000 +#define LG_I2C_FUNC_SMBUS_READ_BLOCK_DATA 0x01000000 +#define LG_I2C_FUNC_SMBUS_WRITE_BLOCK_DATA 0x02000000 +#define LG_I2C_FUNC_SMBUS_READ_I2C_BLOCK 0x04000000 +#define LG_I2C_FUNC_SMBUS_WRITE_I2C_BLOCK 0x08000000 + +typedef struct +{ + uint16_t state; + int16_t fd; + uint32_t addr; + uint32_t flags; + uint32_t funcs; +} lgI2cObj_t, *lgI2cObj_p; + +union lgI2cSmbusData +{ + uint8_t byte; + uint16_t word; + uint8_t block[LG_I2C_SMBUS_BLOCK_MAX + 2]; +}; + +struct lgI2cSmbusIoctlData +{ + uint8_t read_write; + uint8_t command; + uint32_t size; + union lgI2cSmbusData *data; +}; + +typedef struct +{ + lgI2cMsg_t *msgs; /* pointers to pi_i2c_msgs */ + uint32_t nmsgs; /* number of pi_i2c_msgs */ +} lgI2cRdwrIoctlData_t; + +static int xI2cGetPar(const char *inBuf, int *inPos, int inCount, int *esc) +{ + int bytes; + + if (*esc) bytes = 2; else bytes = 1; + + *esc = 0; + + if (*inPos <= (inCount - bytes)) + { + if (bytes == 1) + { + return inBuf[(*inPos)++]; + } + else + { + (*inPos) += 2; + return inBuf[*inPos-2] + (inBuf[*inPos-1]<<8); + } + } + return -1; +} + +static void _lgI2cClose(lgI2cObj_p i2c) +{ + if (i2c) close(i2c->fd); +} + +static int xI2cSmbusAccess( + int fd, char rw, uint8_t cmd, int size, union lgI2cSmbusData *data) +{ + struct lgI2cSmbusIoctlData args; + + LG_DBG(LG_DEBUG_INTERNAL, "rw=%d reg=%d cmd=%d data=%s", + rw, cmd, size, lgDbgBuf2Str(data->byte+1, (char*)data)); + + args.read_write = rw; + args.command = cmd; + args.size = size; + args.data = data; + + return ioctl(fd, LG_I2C_SMBUS, &args); +} + +int lgI2cWriteQuick(int handle, int bit) +{ + int status; + lgI2cObj_p i2c; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d bit=%d", handle, bit); + + if ((unsigned)bit > 1) + PARAM_ERROR(LG_BAD_I2C_PARAM, "bad bit (%d)", bit); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_I2C, (void **)&i2c); + + if (status == LG_OKAY) + { + if (i2c->funcs & LG_I2C_FUNC_SMBUS_QUICK) + { + status = xI2cSmbusAccess( + i2c->fd, bit, 0, LG_I2C_SMBUS_QUICK, NULL); + + if (status < 0) + { + LG_DBG(LG_DEBUG_USER, "error=%d (%m)", status); + status = LG_I2C_WRITE_FAILED; + } + } + else + { + LG_DBG(LG_DEBUG_USER, "write quick not supported by driver"); + status = LG_BAD_SMBUS_CMD; + } + + lgHdlUnlock(handle); + } + + return status; +} + +int lgI2cReadByte(int handle) +{ + union lgI2cSmbusData data; + lgI2cObj_p i2c; + int status; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d", handle); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_I2C, (void **)&i2c); + + if (status == LG_OKAY) + { + if (i2c->funcs & LG_I2C_FUNC_SMBUS_READ_BYTE) + { + status = xI2cSmbusAccess( + i2c->fd, LG_I2C_SMBUS_READ, 0, LG_I2C_SMBUS_BYTE, &data); + + if (status < 0) + { + LG_DBG(LG_DEBUG_USER, "error=%d (%m)", status); + status = LG_I2C_READ_FAILED; + } + else status = 0xFF & data.byte; + } + else + { + LG_DBG(LG_DEBUG_USER, "read byte not supported by driver"); + status = LG_BAD_SMBUS_CMD; + } + + lgHdlUnlock(handle); + } + + return status; +} + + +int lgI2cWriteByte(int handle, int bVal) +{ + int status; + lgI2cObj_p i2c; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d bVal=%d", handle, bVal); + + if ((unsigned) bVal > 0xFF) + PARAM_ERROR(LG_BAD_I2C_PARAM, "bad bVal (%d)", bVal); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_I2C, (void **)&i2c); + + if (status == LG_OKAY) + { + if (i2c->funcs & LG_I2C_FUNC_SMBUS_WRITE_BYTE) + { + status = xI2cSmbusAccess( + i2c->fd, + LG_I2C_SMBUS_WRITE, + bVal, + LG_I2C_SMBUS_BYTE, + NULL); + + if (status < 0) + { + LG_DBG(LG_DEBUG_USER, "error=%d (%m)", status); + status = LG_I2C_WRITE_FAILED; + } + } + else + { + LG_DBG(LG_DEBUG_USER, "write byte not supported by driver"); + status = LG_BAD_SMBUS_CMD; + } + + lgHdlUnlock(handle); + } + + return status; +} + + +int lgI2cReadByteData(int handle, int reg) +{ + union lgI2cSmbusData data; + int status; + lgI2cObj_p i2c; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d reg=%d", handle, reg); + + if ((unsigned)reg > 0xFF) + PARAM_ERROR(LG_BAD_I2C_PARAM, "bad reg (%d)", reg); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_I2C, (void **)&i2c); + + if (status == LG_OKAY) + { + if (i2c->funcs & LG_I2C_FUNC_SMBUS_READ_BYTE_DATA) + { + status = xI2cSmbusAccess(i2c->fd, + LG_I2C_SMBUS_READ, reg, LG_I2C_SMBUS_BYTE_DATA, &data); + + if (status < 0) + { + LG_DBG(LG_DEBUG_USER, "error=%d (%m)", status); + status = LG_I2C_READ_FAILED; + } + else status = 0xFF & data.byte; + } + else + { + LG_DBG(LG_DEBUG_USER, "read byte data not supported by driver"); + status = LG_BAD_SMBUS_CMD; + } + + lgHdlUnlock(handle); + } + + return status; +} + + +int lgI2cWriteByteData(int handle, int reg, int bVal) +{ + union lgI2cSmbusData data; + lgI2cObj_p i2c; + int status; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d reg=%d bVal=%d", handle, reg, bVal); + + if ((unsigned)reg > 0xFF) + PARAM_ERROR(LG_BAD_I2C_PARAM, "bad reg (%d)", reg); + + if ((unsigned)bVal > 0xFF) + PARAM_ERROR(LG_BAD_I2C_PARAM, "bad bVal (%d)", bVal); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_I2C, (void **)&i2c); + + if (status == LG_OKAY) + { + if (i2c->funcs & LG_I2C_FUNC_SMBUS_WRITE_BYTE_DATA) + { + data.byte = bVal; + + status = xI2cSmbusAccess( + i2c->fd, + LG_I2C_SMBUS_WRITE, + reg, + LG_I2C_SMBUS_BYTE_DATA, + &data); + + if (status < 0) + { + LG_DBG(LG_DEBUG_USER, "error=%d (%m)", status); + status = LG_I2C_WRITE_FAILED; + } + } + else + { + LG_DBG(LG_DEBUG_USER, "write byte data not supported by driver"); + status = LG_BAD_SMBUS_CMD; + } + + lgHdlUnlock(handle); + } + + return status; +} + + +int lgI2cReadWordData(int handle, int reg) +{ + union lgI2cSmbusData data; + int status; + lgI2cObj_p i2c; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d reg=%d", handle, reg); + + if ((unsigned)reg > 0xFF) + PARAM_ERROR(LG_BAD_I2C_PARAM, "bad reg (%d)", reg); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_I2C, (void **)&i2c); + + if (status == LG_OKAY) + { + if (i2c->funcs & LG_I2C_FUNC_SMBUS_READ_WORD_DATA) + { + status = (xI2cSmbusAccess( + i2c->fd, + LG_I2C_SMBUS_READ, + reg, + LG_I2C_SMBUS_WORD_DATA, + &data)); + + if (status < 0) + { + LG_DBG(LG_DEBUG_USER, "error=%d (%m)", status); + status = LG_I2C_READ_FAILED; + } + else status = 0xFFFF & data.word; + } + else + { + LG_DBG(LG_DEBUG_USER, "read word data not supported by driver"); + status = LG_BAD_SMBUS_CMD; + } + + lgHdlUnlock(handle); + } + + return status; +} + + +int lgI2cWriteWordData(int handle, int reg, int wVal) +{ + union lgI2cSmbusData data; + + int status; + lgI2cObj_p i2c; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d reg=%d wVal=%d", handle, reg, wVal); + + if ((unsigned)reg > 0xFF) + PARAM_ERROR(LG_BAD_I2C_PARAM, "bad reg (%d)", reg); + + if ((unsigned)wVal > 0xFFFF) + PARAM_ERROR(LG_BAD_I2C_PARAM, "bad wVal (%d)", wVal); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_I2C, (void **)&i2c); + + if (status == LG_OKAY) + { + if (i2c->funcs & LG_I2C_FUNC_SMBUS_WRITE_WORD_DATA) + { + data.word = wVal; + + status = xI2cSmbusAccess( + i2c->fd, + LG_I2C_SMBUS_WRITE, + reg, + LG_I2C_SMBUS_WORD_DATA, + &data); + + if (status < 0) + { + LG_DBG(LG_DEBUG_USER, "error=%d (%m)", status); + status = LG_I2C_WRITE_FAILED; + } + } + else + { + LG_DBG(LG_DEBUG_USER, "SMBUS command not supported by driver"); + status = LG_BAD_SMBUS_CMD; + } + + lgHdlUnlock(handle); + } + + return status; +} + + +int lgI2cProcessCall(int handle, int reg, int wVal) +{ + union lgI2cSmbusData data; + int status; + lgI2cObj_p i2c; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d reg=%d wVal=%d", handle, reg, wVal); + + if ((unsigned)reg > 0xFF) + PARAM_ERROR(LG_BAD_I2C_PARAM, "bad reg (%d)", reg); + + if ((unsigned)wVal > 0xFFFF) + PARAM_ERROR(LG_BAD_I2C_PARAM, "bad wVal (%d)", wVal); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_I2C, (void **)&i2c); + + if (status == LG_OKAY) + { + if (i2c->funcs & LG_I2C_FUNC_SMBUS_PROC_CALL) + { + data.word = wVal; + + status = (xI2cSmbusAccess( + i2c->fd, + LG_I2C_SMBUS_WRITE, + reg, LG_I2C_SMBUS_PROC_CALL, + &data)); + + if (status < 0) + { + LG_DBG(LG_DEBUG_USER, "error=%d (%m)", status); + status = LG_I2C_READ_FAILED; + } + else status = 0xFFFF & data.word; + } + else + { + LG_DBG(LG_DEBUG_USER, "process call not supported by driver"); + status = LG_BAD_SMBUS_CMD; + } + + lgHdlUnlock(handle); + } + + return status; +} + + +int lgI2cReadBlockData(int handle, int reg, char *rxBuf) +{ + union lgI2cSmbusData data; + + int status; + int i; + lgI2cObj_p i2c; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d reg=%d rxBuf=%p", handle, reg, rxBuf); + + if ((unsigned)reg > 0xFF) + PARAM_ERROR(LG_BAD_I2C_PARAM, "bad reg (%d)", reg); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_I2C, (void **)&i2c); + + if (status == LG_OKAY) + { + if (i2c->funcs & LG_I2C_FUNC_SMBUS_READ_BLOCK_DATA) + { + status = (xI2cSmbusAccess( + i2c->fd, + LG_I2C_SMBUS_READ, + reg, + LG_I2C_SMBUS_BLOCK_DATA, + &data)); + + if (status < 0) + { + LG_DBG(LG_DEBUG_USER, "error=%d (%m)", status); + status = LG_I2C_READ_FAILED; + } + else + { + if (data.block[0] <= LG_I2C_SMBUS_BLOCK_MAX) + { + for (i=0; i 0xFF) + PARAM_ERROR(LG_BAD_I2C_PARAM, "bad reg (%d)", reg); + + if ((count < 1) || (count > 32)) + PARAM_ERROR(LG_BAD_I2C_PARAM, "bad count (%d)", count); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_I2C, (void **)&i2c); + + if (status == LG_OKAY) + { + if (i2c->funcs & LG_I2C_FUNC_SMBUS_WRITE_BLOCK_DATA) + { + for (i=1; i<=count; i++) data.block[i] = txBuf[i-1]; + + data.block[0] = count; + + status = xI2cSmbusAccess( + i2c->fd, + LG_I2C_SMBUS_WRITE, + reg, + LG_I2C_SMBUS_BLOCK_DATA, + &data); + + if (status < 0) + { + LG_DBG(LG_DEBUG_USER, "error=%d (%m)", status); + status = LG_I2C_WRITE_FAILED; + } + } + else + { + LG_DBG(LG_DEBUG_USER, "write block data not supported by driver"); + status = LG_BAD_SMBUS_CMD; + } + + lgHdlUnlock(handle); + } + + return status; +} + + +int lgI2cBlockProcessCall( + int handle, int reg, char *buf, int count) +{ + union lgI2cSmbusData data; + + int i; + int status; + lgI2cObj_p i2c; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d reg=%d count=%d [%s]", + handle, reg, count, lgDbgBuf2Str(count, buf)); + + if ((unsigned)reg > 0xFF) + PARAM_ERROR(LG_BAD_I2C_PARAM, "bad reg (%d)", reg); + + if ((count < 1) || (count > 32)) + PARAM_ERROR(LG_BAD_I2C_PARAM, "bad count (%d)", count); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_I2C, (void **)&i2c); + + if (status == LG_OKAY) + { + if (i2c->funcs & LG_I2C_FUNC_SMBUS_PROC_CALL) + { + for (i=1; i<=count; i++) data.block[i] = buf[i-1]; + + data.block[0] = count; + + status = xI2cSmbusAccess( + i2c->fd, LG_I2C_SMBUS_WRITE, reg, + LG_I2C_SMBUS_BLOCK_PROC_CALL, &data); + + if (status < 0) + { + LG_DBG(LG_DEBUG_USER, "error=%d (%m)", status); + status = LG_I2C_READ_FAILED; + } + else + { + if (data.block[0] <= LG_I2C_SMBUS_BLOCK_MAX) + { + for (i=0; i 0xFF) + PARAM_ERROR(LG_BAD_I2C_PARAM, "bad reg (%d)", reg); + + if ((count < 1) || (count > 32)) + PARAM_ERROR(LG_BAD_I2C_PARAM, "bad count (%d)", count); + + if (count == 32) + size = LG_I2C_SMBUS_I2C_BLOCK_BROKEN; + else + size = LG_I2C_SMBUS_I2C_BLOCK_DATA; + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_I2C, (void **)&i2c); + + if (status == LG_OKAY) + { + if (i2c->funcs & LG_I2C_FUNC_SMBUS_READ_I2C_BLOCK) + { + data.block[0] = count; + + status = xI2cSmbusAccess( + i2c->fd, LG_I2C_SMBUS_READ, reg, size, &data); + + if (status < 0) + { + LG_DBG(LG_DEBUG_USER, "error=%d (%m)", status); + status = LG_I2C_READ_FAILED; + } + else + { + if (data.block[0] <= LG_I2C_SMBUS_I2C_BLOCK_MAX) + { + for (i=0; i 0xFF) + PARAM_ERROR(LG_BAD_I2C_PARAM, "bad reg (%d)", reg); + + if ((count < 1) || (count > 32)) + PARAM_ERROR(LG_BAD_I2C_PARAM, "bad count (%d)", count); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_I2C, (void **)&i2c); + + if (status == LG_OKAY) + { + if (i2c->funcs & LG_I2C_FUNC_SMBUS_WRITE_I2C_BLOCK) + { + for (i=1; i<=count; i++) data.block[i] = txBuf[i-1]; + + data.block[0] = count; + + status = xI2cSmbusAccess( + i2c->fd, + LG_I2C_SMBUS_WRITE, + reg, + LG_I2C_SMBUS_I2C_BLOCK_BROKEN, + &data); + + if (status < 0) + { + LG_DBG(LG_DEBUG_USER, "error=%d (%m)", status); + status = LG_I2C_WRITE_FAILED; + } + } + else + { + LG_DBG(LG_DEBUG_USER, "write I2C block not supported by driver"); + status = LG_BAD_SMBUS_CMD; + } + + lgHdlUnlock(handle); + } + + return status; +} + +int lgI2cWriteDevice(int handle, const char *txBuf, int count) +{ + int bytes; + lgI2cObj_p i2c; + int status; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d count=%d [%s]", + handle, count, lgDbgBuf2Str(count, txBuf)); + + if ((count < 1) || (count > LG_MAX_I2C_DEVICE_COUNT)) + PARAM_ERROR(LG_BAD_I2C_PARAM, "bad count (%d)", count); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_I2C, (void **)&i2c); + + if (status == LG_OKAY) + { + bytes = write(i2c->fd, txBuf, count); + + if (bytes != count) + { + LG_DBG(LG_DEBUG_USER, "error=%d (%m)", bytes); + status = LG_I2C_WRITE_FAILED; + } + + lgHdlUnlock(handle); + } + + return status; +} + +int lgI2cReadDevice(int handle, char *rxBuf, int count) +{ + lgI2cObj_p i2c; + int status; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d count=%d rxBuf=%p", + handle, count, rxBuf); + + if ((count < 1) || (count > LG_MAX_I2C_DEVICE_COUNT)) + PARAM_ERROR(LG_BAD_I2C_PARAM, "bad count (%d)", count); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_I2C, (void **)&i2c); + + if (status == LG_OKAY) + { + status = read(i2c->fd, rxBuf, count); + + if (status != count) + { + LG_DBG(LG_DEBUG_USER, "error=%d (%m)", status); + status = LG_I2C_READ_FAILED; + } + + lgHdlUnlock(handle); + } + + return status; +} + +int lgI2cOpen(int i2cDev, int i2cAddr, int i2cFlags) +{ + int handle; + char dev[32]; + int fd; + uint32_t funcs; + lgI2cObj_p i2c; + + LG_DBG(LG_DEBUG_TRACE, "i2cDev=%d i2cAddr=%d flags=0x%X", + i2cDev, i2cAddr, i2cFlags); + + if ((unsigned)i2cAddr > LG_MAX_I2C_ADDR) + PARAM_ERROR(LG_BAD_I2C_ADDR, "bad I2C address (%d)", i2cAddr); + + if (i2cFlags) + PARAM_ERROR(LG_BAD_I2C_FLAGS, "bad I2C flags (0x%X)", i2cFlags); + + sprintf(dev, "/dev/i2c-%d", i2cDev); + + if ((fd = open(dev, O_RDWR)) < 0) + { + return LG_BAD_I2C_BUS; + } + + if (ioctl(fd, LG_I2C_SLAVE, i2cAddr) < 0) + { + close(fd); + return LG_I2C_OPEN_FAILED; + } + + if (ioctl(fd, LG_I2C_FUNCS, &funcs) < 0) + { + funcs = -1; /* assume all smbus commands allowed */ + } + + LG_DBG(LG_DEBUG_ALLOC, "alloc i2c: *%p", (void*)i2c); + + handle = lgHdlAlloc( + LG_HDL_TYPE_I2C, sizeof(lgI2cObj_t), (void **)&i2c, _lgI2cClose); + + if (handle < 0) + { + close(fd); + return LG_NO_MEMORY; + } + + i2c->fd = fd; + i2c->addr = i2cAddr; + i2c->flags = i2cFlags; + i2c->funcs = funcs; + + return handle; +} + +int lgI2cClose(int handle) +{ + int status; + lgI2cObj_p i2c; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d", handle); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_I2C, (void **)&i2c); + + if (status == LG_OKAY) + { + status = lgHdlFree(handle, LG_HDL_TYPE_I2C); + + lgHdlUnlock(handle); + } + + return status; +} + +int lgI2cSegments( + int handle, lgI2cMsg_t *segs, int numSegs) +{ + lgI2cRdwrIoctlData_t rdwr; + lgI2cObj_p i2c; + int status; + + LG_DBG(LG_DEBUG_USER, "handle=%d", handle); + + if (segs == NULL) + PARAM_ERROR(LG_BAD_POINTER, "null segments"); + + if (numSegs > LG_I2C_RDRW_IOCTL_MAX_MSGS) + PARAM_ERROR(LG_TOO_MANY_SEGS, "too many segments (%d)", numSegs); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_I2C, (void **)&i2c); + + if (status == LG_OKAY) + { + rdwr.msgs = segs; + rdwr.nmsgs = numSegs; + + status = ioctl(i2c->fd, LG_I2C_RDWR, &rdwr); + + if (status < 0) status = LG_BAD_I2C_SEG; + + lgHdlUnlock(handle); + } + + return status; +} + +static int xSegments(int fd, lgI2cMsg_t *segs, int numSegs) +{ + lgI2cRdwrIoctlData_t rdwr; + int status; + + rdwr.msgs = segs; + rdwr.nmsgs = numSegs; + + status = ioctl(fd, LG_I2C_RDWR, &rdwr); + + if (status < 0) status = LG_BAD_I2C_SEG; + + return status; +} + + +int lgI2cZip( + int handle, const char *inBuf, int inCount, char *outBuf, int outCount) +{ + int numSegs, inPos, outPos, bytes, flags, addr; + int esc, setesc; + lgI2cMsg_t segs[LG_I2C_RDRW_IOCTL_MAX_MSGS]; + lgI2cObj_p i2c; + int status; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d inBuf=%s outBuf=%p len=%d", + handle, lgDbgBuf2Str(inCount, inBuf), outBuf, outCount); + + if (!inBuf || (inCount<1)) + PARAM_ERROR(LG_BAD_POINTER, "input buffer can't be NULL"); + + if (!outBuf && outCount) + PARAM_ERROR(LG_BAD_POINTER, "output buffer can't be NULL"); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_I2C, (void **)&i2c); + + if (status == LG_OKAY) + { + numSegs = 0; + + inPos = 0; + outPos = 0; + status = 0; + + addr = i2c->addr; + flags = 0; + esc = 0; + setesc = 0; + + while (!status && (inPos < inCount)) + { + LG_DBG(LG_DEBUG_INTERNAL, + "status=%d inpos=%d inlen=%d cmd=%d addr=%d flags=%x", + status, inPos, inCount, inBuf[inPos], addr, flags); + + switch (inBuf[inPos++]) + { + case LG_I2C_END: + status = 1; + break; + + case LG_I2C_ADDR: + addr = xI2cGetPar(inBuf, &inPos, inCount, &esc); + if (addr < 0) status = LG_BAD_I2C_CMD; + break; + + case LG_I2C_FLAGS: + /* cheat to force two byte flags */ + esc = 1; + flags = xI2cGetPar(inBuf, &inPos, inCount, &esc); + if (flags < 0) status = LG_BAD_I2C_CMD; + break; + + case LG_I2C_ESC: + setesc = 1; + break; + + case LG_I2C_READ: + bytes = xI2cGetPar(inBuf, &inPos, inCount, &esc); + + if (bytes >= 0) + { + if ((bytes + outPos) < outCount) + { + segs[numSegs].addr = addr; + segs[numSegs].flags = (flags|1); + segs[numSegs].len = bytes; + segs[numSegs].buf = (uint8_t *)(outBuf + outPos); + outPos += bytes; + numSegs++; + if (numSegs >= LG_I2C_RDRW_IOCTL_MAX_MSGS) + { + status = xSegments(i2c->fd, segs, numSegs); + if (status >= 0) status = 0; /* continue */ + numSegs = 0; + } + } + else status = LG_BAD_I2C_RLEN; + } + else status = LG_BAD_I2C_RLEN; + break; + + case LG_I2C_WRITE: + + bytes = xI2cGetPar(inBuf, &inPos, inCount, &esc); + + if (bytes >= 0) + { + if ((bytes + inPos) < inCount) + { + segs[numSegs].addr = addr; + segs[numSegs].flags = (flags&0xfffe); + segs[numSegs].len = bytes; + segs[numSegs].buf = (uint8_t *)(inBuf + inPos); + inPos += bytes; + numSegs++; + if (numSegs >= LG_I2C_RDRW_IOCTL_MAX_MSGS) + { + status = xSegments(i2c->fd, segs, numSegs); + if (status >= 0) status = 0; /* continue */ + numSegs = 0; + } + } + else status = LG_BAD_I2C_WLEN; + } + else status = LG_BAD_I2C_WLEN; + break; + + default: + status = LG_BAD_I2C_CMD; + } + + if (setesc) esc = 1; else esc = 0; + + setesc = 0; + } + + if (status >= 0) + { + if (numSegs) status = xSegments(i2c->fd, segs, numSegs); + } + + if (status >= 0) status = outPos; + + lgHdlUnlock(handle); + } + + return status; +} + diff --git a/lgMD5.c b/lgMD5.c new file mode 100644 index 0000000..327ce06 --- /dev/null +++ b/lgMD5.c @@ -0,0 +1,313 @@ +/* +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 +*/ + + +/* +This code is a modification of the following: +http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5 +*/ + +#include +#include +#include + +#include "lgpio.h" + +#include "lgMD5.h" +#include "lgCfg.h" + +/* + * The basic MD5 functions. + * + * F and G are optimized compared to their RFC 1321 definitions for + * architectures that lack an AND-NOT instruction, just like in Colin Plumb's + * implementation. + */ +#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) +#define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y)))) +#define H(x, y, z) (((x) ^ (y)) ^ (z)) +#define H2(x, y, z) ((x) ^ ((y) ^ (z))) +#define I(x, y, z) ((y) ^ ((x) | ~(z))) + +/* + * The MD5 transformation for all four rounds. + */ +#define STEP(f, a, b, c, d, x, t, s) \ + (a) += f((b), (c), (d)) + (x) + (t); \ + (a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); \ + (a) += (b); + +/* + * SET reads 4 input bytes in little-endian byte order and stores them in a + * properly aligned word in host byte order. + * + */ +#define SET(n) \ + (ctx->block[(n)] = \ + (MD5_u32plus)ptr[(n) * 4] | \ + ((MD5_u32plus)ptr[(n) * 4 + 1] << 8) | \ + ((MD5_u32plus)ptr[(n) * 4 + 2] << 16) | \ + ((MD5_u32plus)ptr[(n) * 4 + 3] << 24)) + +#define GET(n) \ + (ctx->block[(n)]) + +/* + * This processes one or more 64-byte data blocks, but does NOT update the bit + * counters. There are no alignment requirements. + */ +static const void *body(lgMd5_t *ctx, const void *data, unsigned long size) +{ + const unsigned char *ptr; + MD5_u32plus a, b, c, d; + MD5_u32plus saved_a, saved_b, saved_c, saved_d; + + ptr = (const unsigned char *)data; + + a = ctx->a; + b = ctx->b; + c = ctx->c; + d = ctx->d; + + do { + saved_a = a; + saved_b = b; + saved_c = c; + saved_d = d; + +/* Round 1 */ + STEP(F, a, b, c, d, SET(0), 0xd76aa478, 7) + STEP(F, d, a, b, c, SET(1), 0xe8c7b756, 12) + STEP(F, c, d, a, b, SET(2), 0x242070db, 17) + STEP(F, b, c, d, a, SET(3), 0xc1bdceee, 22) + STEP(F, a, b, c, d, SET(4), 0xf57c0faf, 7) + STEP(F, d, a, b, c, SET(5), 0x4787c62a, 12) + STEP(F, c, d, a, b, SET(6), 0xa8304613, 17) + STEP(F, b, c, d, a, SET(7), 0xfd469501, 22) + STEP(F, a, b, c, d, SET(8), 0x698098d8, 7) + STEP(F, d, a, b, c, SET(9), 0x8b44f7af, 12) + STEP(F, c, d, a, b, SET(10), 0xffff5bb1, 17) + STEP(F, b, c, d, a, SET(11), 0x895cd7be, 22) + STEP(F, a, b, c, d, SET(12), 0x6b901122, 7) + STEP(F, d, a, b, c, SET(13), 0xfd987193, 12) + STEP(F, c, d, a, b, SET(14), 0xa679438e, 17) + STEP(F, b, c, d, a, SET(15), 0x49b40821, 22) + +/* Round 2 */ + STEP(G, a, b, c, d, GET(1), 0xf61e2562, 5) + STEP(G, d, a, b, c, GET(6), 0xc040b340, 9) + STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14) + STEP(G, b, c, d, a, GET(0), 0xe9b6c7aa, 20) + STEP(G, a, b, c, d, GET(5), 0xd62f105d, 5) + STEP(G, d, a, b, c, GET(10), 0x02441453, 9) + STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14) + STEP(G, b, c, d, a, GET(4), 0xe7d3fbc8, 20) + STEP(G, a, b, c, d, GET(9), 0x21e1cde6, 5) + STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9) + STEP(G, c, d, a, b, GET(3), 0xf4d50d87, 14) + STEP(G, b, c, d, a, GET(8), 0x455a14ed, 20) + STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5) + STEP(G, d, a, b, c, GET(2), 0xfcefa3f8, 9) + STEP(G, c, d, a, b, GET(7), 0x676f02d9, 14) + STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20) + +/* Round 3 */ + STEP(H, a, b, c, d, GET(5), 0xfffa3942, 4) + STEP(H2, d, a, b, c, GET(8), 0x8771f681, 11) + STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16) + STEP(H2, b, c, d, a, GET(14), 0xfde5380c, 23) + STEP(H, a, b, c, d, GET(1), 0xa4beea44, 4) + STEP(H2, d, a, b, c, GET(4), 0x4bdecfa9, 11) + STEP(H, c, d, a, b, GET(7), 0xf6bb4b60, 16) + STEP(H2, b, c, d, a, GET(10), 0xbebfbc70, 23) + STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4) + STEP(H2, d, a, b, c, GET(0), 0xeaa127fa, 11) + STEP(H, c, d, a, b, GET(3), 0xd4ef3085, 16) + STEP(H2, b, c, d, a, GET(6), 0x04881d05, 23) + STEP(H, a, b, c, d, GET(9), 0xd9d4d039, 4) + STEP(H2, d, a, b, c, GET(12), 0xe6db99e5, 11) + STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16) + STEP(H2, b, c, d, a, GET(2), 0xc4ac5665, 23) + +/* Round 4 */ + STEP(I, a, b, c, d, GET(0), 0xf4292244, 6) + STEP(I, d, a, b, c, GET(7), 0x432aff97, 10) + STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15) + STEP(I, b, c, d, a, GET(5), 0xfc93a039, 21) + STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6) + STEP(I, d, a, b, c, GET(3), 0x8f0ccc92, 10) + STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15) + STEP(I, b, c, d, a, GET(1), 0x85845dd1, 21) + STEP(I, a, b, c, d, GET(8), 0x6fa87e4f, 6) + STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10) + STEP(I, c, d, a, b, GET(6), 0xa3014314, 15) + STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21) + STEP(I, a, b, c, d, GET(4), 0xf7537e82, 6) + STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10) + STEP(I, c, d, a, b, GET(2), 0x2ad7d2bb, 15) + STEP(I, b, c, d, a, GET(9), 0xeb86d391, 21) + + a += saved_a; + b += saved_b; + c += saved_c; + d += saved_d; + + ptr += 64; + } while (size -= 64); + + ctx->a = a; + ctx->b = b; + ctx->c = c; + ctx->d = d; + + return ptr; +} + +void lgMd5Init(lgMd5_p ctx) +{ + ctx->a = 0x67452301; + ctx->b = 0xefcdab89; + ctx->c = 0x98badcfe; + ctx->d = 0x10325476; + + ctx->lo = 0; + ctx->hi = 0; +} + +void lgMd5Update(lgMd5_p ctx, const void *data, unsigned long size) +{ + MD5_u32plus saved_lo; + unsigned long used, available; + + saved_lo = ctx->lo; + if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo) + ctx->hi++; + ctx->hi += size >> 29; + + used = saved_lo & 0x3f; + + if (used) { + available = 64 - used; + + if (size < available) { + memcpy(&ctx->buffer[used], data, size); + return; + } + + memcpy(&ctx->buffer[used], data, available); + data = (const unsigned char *)data + available; + size -= available; + body(ctx, ctx->buffer, 64); + } + + if (size >= 64) { + data = body(ctx, data, size & ~(unsigned long)0x3f); + size &= 0x3f; + } + + memcpy(ctx->buffer, data, size); +} + +#define OUT(dst, src) \ + (dst)[0] = (unsigned char)(src); \ + (dst)[1] = (unsigned char)((src) >> 8); \ + (dst)[2] = (unsigned char)((src) >> 16); \ + (dst)[3] = (unsigned char)((src) >> 24); + +void lgMd5Final(lgMd5_p ctx, unsigned char *result) +{ + unsigned long used, available; + unsigned char tmp_result[16]; + int i; + + used = ctx->lo & 0x3f; + + ctx->buffer[used++] = 0x80; + + available = 64 - used; + + if (available < 8) { + memset(&ctx->buffer[used], 0, available); + body(ctx, ctx->buffer, 64); + used = 0; + available = 64; + } + + memset(&ctx->buffer[used], 0, available - 8); + + ctx->lo <<= 3; + OUT(&ctx->buffer[56], ctx->lo) + OUT(&ctx->buffer[60], ctx->hi) + + body(ctx, ctx->buffer, 64); + + OUT(&tmp_result[0], ctx->a) + OUT(&tmp_result[4], ctx->b) + OUT(&tmp_result[8], ctx->c) + OUT(&tmp_result[12], ctx->d) + + memset(ctx, 0, sizeof(*ctx)); + + // we want a 32 byte hex digest as the result + + for (i=0; i<16; i++) + sprintf((char *)result+i+i, "%02x", tmp_result[i]); +} + +void lgMd5UserHash( + char *user, char *salt1, char *salt2, char *secretFile, char *hash) +{ + lgMd5_t md5; + lgCfg_p sCfg; + char *secret=NULL; + char sPath[1024]; + + if (strlen(secretFile) == 0) + { + snprintf(sPath, sizeof(sPath), "%s/.lg_secret", getenv("HOME")); + secretFile = sPath; + } + + sCfg = lgCfgRead(secretFile); + + if (sCfg != NULL) + { + secret = lgCfgGetValue(sCfg, "global", user); + + if (secret != NULL) + { + lgMd5Init(&md5); + lgMd5Update(&md5, salt1, LG_SALT_LEN-1); + lgMd5Update(&md5, secret, strlen(secret)); + lgMd5Update(&md5, salt2, LG_SALT_LEN-1); + lgMd5Final(&md5, (unsigned char *)hash); + } + + lgCfgFree(sCfg); + } +} + diff --git a/lgMD5.h b/lgMD5.h new file mode 100644 index 0000000..9ae962e --- /dev/null +++ b/lgMD5.h @@ -0,0 +1,55 @@ +/* +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 +*/ + +#ifndef LG_MD5_H +#define LG_MD5_H + +/* +This code is a modification of the following: +http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5 +*/ + +/* Any 32-bit or wider unsigned integer data type will do */ +typedef unsigned int MD5_u32plus; + +typedef struct +{ + MD5_u32plus lo, hi; + MD5_u32plus a, b, c, d; + unsigned char buffer[64]; + MD5_u32plus block[16]; +} lgMd5_t, *lgMd5_p; + +void lgMd5Init (lgMd5_p ctx); +void lgMd5Update(lgMd5_p ctx, const void *data, unsigned long size); +void lgMd5Final (lgMd5_p ctx, unsigned char *result); + +void lgMd5UserHash( + char *user, char *salt1, char *salt2, char *secretFile, char *hash); + +#endif + diff --git a/lgNotify.c b/lgNotify.c new file mode 100644 index 0000000..5f0a5d4 --- /dev/null +++ b/lgNotify.c @@ -0,0 +1,279 @@ +/* +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 +*/ + +#define _GNU_SOURCE /* needed for pipes */ + +#include +#include +#include +#include +#include +#include +#include + +#include "lgpio.h" + +#include "lgDbg.h" +#include "lgHdl.h" + +static void xCreatePipe(const char *name, int perm) +{ + unlink(name); + + mkfifo(name, perm); + + if (chmod(name, perm) < 0) + { + LG_DBG(LG_DEBUG_ALWAYS, + "Can't set permissions (%d) for %s, %m", perm, name); + return; + } +} + + +static void _notifyClose(lgNotify_t *h) +{ + char fifo[128]; + + LG_DBG(LG_DEBUG_INTERNAL, "fd=%d pipe_no=%d objp=*%p", + h->fd, h->pipe_number, h); + + if (h->fd >= 0) close(h->fd); + + if (h->pipe_number) + { + LG_DBG(LG_DEBUG_INTERNAL, "close notify pipe %d", h->fd); + snprintf(fifo, sizeof(fifo), "/dev/lg%d", h->pipe_number-1); + unlink(fifo); + } +} + +/* ----------------------------------------------------------------------- */ + +void lgNotifyCloseOrphans(int slot, int fd) +{ + static int *handles; + static int maxHandles = 20; + int i, numHandles; + lgNotify_t *h; + int status; + + /* Check for and close any orphaned notifications. */ + + if (handles == NULL) handles = malloc(sizeof(int) * maxHandles); + + numHandles = lgHdlGetHandlesForType(LG_HDL_TYPE_NOTIFY, handles, maxHandles); + + if (numHandles > maxHandles) numHandles = maxHandles; + + for (i=0; i= 0) && + (h->state >= LG_NOTIFY_RUNNING) && + (h->fd == fd)) + { + LG_DBG(LG_DEBUG_USER, "closed orphaned fd=%d (handle=%d)", + fd, handles[i]); + lgHdlFree(handles[i], LG_HDL_TYPE_NOTIFY); + } + + lgHdlUnlock(handles[i]); + } +} + +/* ----------------------------------------------------------------------- */ + +int lgNotifyOpenWithSize(int bufSize) +{ + int i, fd; + char name[LG_MAX_PATH]; + lgNotify_t *h; + int handle; + + LG_DBG(LG_DEBUG_INTERNAL, "bufSize=%d", bufSize); + + handle = lgHdlAlloc( + LG_HDL_TYPE_NOTIFY, sizeof(lgNotify_t), (void**)&h, _notifyClose); + + if (handle < 0) {return LG_NO_MEMORY;} + + snprintf(name, sizeof(name), "%s/lgd-nfy%d", lguGetWorkDir(), handle); + + xCreatePipe(name, 0664); + + fd = open(name, O_RDWR|O_NONBLOCK); + + h->fd = fd; + h->pipe_number = handle+1; // 0 is reserved for no pipe + + if (fd < 0) + { + lgHdlFree(handle, LG_HDL_TYPE_NOTIFY); + PARAM_ERROR(LG_BAD_PATHNAME, "open %s failed (%m)", name); + } + + if (bufSize != 0) + { + i = fcntl(fd, F_SETPIPE_SZ, bufSize); + if (i != bufSize) + { + lgHdlFree(handle, LG_HDL_TYPE_NOTIFY); + PARAM_ERROR(LG_BAD_PATHNAME, + "fcntl %s size %d failed (%m)", name, bufSize); + } + } + + h->max_emits = MAX_EMITS; + h->state = LG_NOTIFY_RUNNING; + + lgNotifyCloseOrphans(handle, fd); + + return handle; +} + +int lgNotifyOpen(void) +{ + LG_DBG(LG_DEBUG_TRACE, ""); + + return lgNotifyOpenWithSize(0); +} + +/* ----------------------------------------------------------------------- */ + +int lgNotifyOpenInBand(int fd) +{ + int handle; + lgNotify_t *h; + + LG_DBG(LG_DEBUG_TRACE, "fd=%d", fd); + + handle = lgHdlAlloc( + LG_HDL_TYPE_NOTIFY, sizeof(lgNotify_t), (void**)&h, _notifyClose); + + if (handle < 0) {return LG_NO_MEMORY;} + + h->fd = fd; + h->pipe_number = 0; + h->max_emits = MAX_EMITS; + h->state = LG_NOTIFY_RUNNING; + + //lgNotifyCloseOrphans(handle, fd); + + return handle; +} + + +/* ----------------------------------------------------------------------- */ + +int lgNotifyResume(int handle) +{ + int status; + lgNotify_t *h; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d", handle); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_NOTIFY, (void **)&h); + + if (status == LG_OKAY) + { + if (h->state > LG_NOTIFY_CLOSING) h->state = LG_NOTIFY_RUNNING; + else + { + LG_DBG(LG_DEBUG_USER, "bad handle (%d)", handle); + status = LG_BAD_HANDLE; + } + + lgHdlUnlock(handle); + } + + return status; +} + + +/* ----------------------------------------------------------------------- */ + +int lgNotifyPause (int handle) +{ + int status; + lgNotify_t *h; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d", handle); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_NOTIFY, (void **)&h); + + if (status == LG_OKAY) + { + if (h->state > LG_NOTIFY_CLOSING) h->state = LG_NOTIFY_PAUSED; + else + { + LG_DBG(LG_DEBUG_USER, "bad handle (%d)", handle); + status = LG_BAD_HANDLE; + } + + lgHdlUnlock(handle); + } + + return status; +} + + +/* ----------------------------------------------------------------------- */ + +int lgNotifyClose(int handle) +{ + int status; + lgNotify_t *h; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d", handle); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_NOTIFY, (void **)&h); + + if (status == LG_OKAY) + { + if (h->state > LG_NOTIFY_CLOSING) h->state = LG_NOTIFY_CLOSING; + else + { + LG_DBG(LG_DEBUG_USER, "bad handle (%d)", handle); + status = LG_BAD_HANDLE; + } + + /* actual close done in alert thread */ + status = lgHdlFree(handle, LG_HDL_TYPE_NOTIFY); + + lgHdlUnlock(handle); + } + + return status; +} + + diff --git a/lgPthAlerts.c b/lgPthAlerts.c new file mode 100644 index 0000000..6376b51 --- /dev/null +++ b/lgPthAlerts.c @@ -0,0 +1,654 @@ +/* +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 +*/ + +#define _GNU_SOURCE /* needed for ppoll */ + +#include +#include +#include +#include +#include +#include + +#include "lgDbg.h" +#include "lgHdl.h" +#include "lgGpio.h" +#include "lgPthAlerts.h" + +#define LG_MAX_ALERTS 2000 +#define LG_GPIO_MAX_ALERTS_PER_READ 128 + +pthread_t pthAlert; +pthread_mutex_t lgAlertMutex = PTHREAD_MUTEX_INITIALIZER; +volatile lgAlertRec_p alertRec = NULL; +pthread_mutex_t lgAlertCondMutex = PTHREAD_MUTEX_INITIALIZER; +pthread_cond_t lgAlertCond = PTHREAD_COND_INITIALIZER; +int pthAlertRunning = LG_THREAD_NONE; + +lgGpioAlert_t aBuf[LG_MAX_ALERTS]; + +static uint64_t xNowTimestamp; + +static void xWaitForSignal(pthread_cond_t *cond, pthread_mutex_t *mutex) +{ + pthread_mutex_lock(mutex); + pthread_cond_wait(cond, mutex); + pthread_mutex_unlock(mutex); +} + +void xSendUnwaitSignal(pthread_cond_t *cond, pthread_mutex_t *mutex) +{ + pthread_mutex_lock(mutex); + pthread_cond_signal(cond); + pthread_mutex_unlock(mutex); +} + +int tscomp(const void *p1, const void *p2) +{ + const lgGpioAlert_t *e1 = p1; + const lgGpioAlert_t *e2 = p2; + + return e1->report.timestamp - e2->report.timestamp; +} + +void emitNotifications(int count) +{ + static int maxHandles = 20; + static int *handles = NULL; + lgGpioReport_t report[LG_MAX_ALERTS]; + int numHandles; + int i; + int status; + lgNotify_t *h; + int emit; + int d; + int sent; + int max_emits; + int err; + + if (handles == NULL) handles = malloc(sizeof(int) * maxHandles); + + numHandles = lgHdlGetHandlesForType( + LG_HDL_TYPE_NOTIFY, handles, maxHandles); + + if (numHandles > maxHandles) + { + LG_DBG(LG_DEBUG_ALWAYS, "too many notifications"); + numHandles = maxHandles; + } + + for (i=0; istate == LG_NOTIFY_CLOSING) + { + lgHdlFree(handles[i], LG_HDL_TYPE_NOTIFY); + } + else if (h->state >= LG_NOTIFY_RUNNING) + { + emit = 0; + + if (h->state == LG_NOTIFY_RUNNING) + { + for (d=0; dmax_emits; + + sent = 0; + + while (emit > 0) + { + if (emit > max_emits) + { + err = write(h->fd, + report+sent, + max_emits*sizeof(lgGpioReport_t)); + + if (err != (max_emits*sizeof(lgGpioReport_t))) + { + if (err < 0) + { + if ((errno != EAGAIN) && (errno != EWOULDBLOCK)) + { + /* serious error, no point continuing */ + + LG_DBG(LG_DEBUG_ALWAYS, "fd=%d err=%d errno=%d", + h->fd, err, errno); + + LG_DBG(LG_DEBUG_ALWAYS, "%s", strerror(errno)); + + h->state = LG_NOTIFY_CLOSING; + break; + } + //else gpioStats.wouldBlockPipeWrite++; + } + else + { + //gpioStats.shortPipeWrite++; + LG_DBG(LG_DEBUG_ALWAYS, "sent %zd, asked for %d", + err/sizeof(lgGpioReport_t), max_emits); + } + } + else + { + //gpioStats.goodPipeWrite++; + } + + sent += max_emits; + emit -= max_emits; + } + else + { + err = write(h->fd, + report+sent, + emit*sizeof(lgGpioReport_t)); + + if (err != (emit*sizeof(lgGpioReport_t))) + { + if (err < 0) + { + if ((errno != EAGAIN) && (errno != EWOULDBLOCK)) + { + LG_DBG(LG_DEBUG_ALWAYS, "fd=%d err=%d errno=%d", + h->fd, err, errno); + + LG_DBG(LG_DEBUG_ALWAYS, "%s", strerror(errno)); + + /* serious error, no point continuing */ + h->state = LG_NOTIFY_CLOSING; + break; + } + //else gpioStats.wouldBlockPipeWrite++; + } + else + { + //gpioStats.shortPipeWrite++; + LG_DBG(LG_DEBUG_ALWAYS, "sent %zd, asked for %d", + err/sizeof(lgGpioReport_t), emit); + } + } + else + { + //gpioStats.goodPipeWrite++; + } + + sent += emit; + emit = 0; + } + } + } + } + + lgHdlUnlock(handles[i]); + } +} + +int emit(int count, uint64_t tmax) +{ + int i; + + for (i=0; i tmax) break; + } + + if (lgGpioSamplesFunc) + (lgGpioSamplesFunc)(i, aBuf, lgGpioSamplesUserdata); + + emitNotifications(i); + + return i; +} + +void printbuf(int count, char *str) +{ + int i; + + fprintf(stderr, "%s\n", str); + for (i=0; i 0) + { + fprintf(stderr, "%s\n%"PRIu64" %d %d %d\n", + str, aBuf[i-1].report.timestamp, aBuf[i-1].report.level, + aBuf[i-1].report.chip, aBuf[i-1].report.gpio); + fprintf(stderr, "%"PRIu64" %d %d %d\n\n", + aBuf[i].report.timestamp, aBuf[i].report.level, + aBuf[i].report.chip, aBuf[i].report.gpio); + } + } +} + +void xDebWatEvt( + lgAlertRec_p p, uint64_t ts, int *cp, struct gpioevent_data *ep) +{ + int64_t nano_diff; + + if (p->debounce_nanos && !p->debounced) + { + /* + Only report stable edges. A stable edge is defined as one + which has not changed for debounce nanoseconds. + + The following rules are applied. + + 1. If only rising edges are being monitored a rising edge + is reported if and only no other edge is detected for at + least debounce nanoseconds after it occurred. + + 2. If only falling edges are being monitored a falling edge + is reported if and only no other edge is detected for at + least debounce nanoseconds after it occurred. + + 3. If both edges are being monitored an edge is reported + if and only no other edge is detected for at least debounce + nanoseconds after it occurred and the edge is different to + the previously reported edge. + */ + + nano_diff = ts - p->last_evt_ts; + + if (nano_diff > p->debounce_nanos) + { + /* GPIO stable for debounce period */ + + if ((p->eFlags != LG_BOTH_EDGES) || + (p->last_rpt_lv != p->last_evt_lv)) + { + aBuf[*cp].report.timestamp = p->last_evt_ts + p->debounce_nanos; + aBuf[*cp].report.level = p->last_evt_lv; + aBuf[*cp].report.chip = p->chip->gpiochip; + aBuf[*cp].report.gpio = p->gpio; + aBuf[*cp].report.flags = 0; + aBuf[*cp].nfyHandle = p->nfyHandle; + + if (++(*cp) < LG_MAX_ALERTS) + { + p->last_rpt_ts = p->last_evt_ts + p->debounce_nanos; + p->last_rpt_lv = p->last_evt_lv; + p->debounced = 1; + p->watchdogd = 0; + } + else + { + --(*cp); + LG_DBG(LG_DEBUG_ALWAYS, "more than %d alerts", LG_MAX_ALERTS); + } + } + } + } + + if (p->watchdog_nanos && !p->watchdogd) + { + /* + Ensure that a watchdog report is sent for a GPIO in the + absence of edge reports. + + The following rule is applied. + + If no edge report has been issued for the GPIO in + the last watchdog nanoseconds then one watchdog report + is generated. + + Note that only one watchdog report is sent until the + watchdog is reset by the sending of an edge report. + */ + + nano_diff = ts - p->last_rpt_ts; + + if (nano_diff > p->watchdog_nanos) + { + aBuf[*cp].report.timestamp = ts; + aBuf[*cp].report.level = LG_TIMEOUT; + aBuf[*cp].report.chip = p->chip->gpiochip; + aBuf[*cp].report.gpio = p->gpio; + aBuf[*cp].report.flags = 0; + aBuf[*cp].nfyHandle = p->nfyHandle; + + if (++(*cp) < LG_MAX_ALERTS) + { + p->watchdogd = 1; + p->last_rpt_ts = ts; + p->last_rpt_lv = LG_TIMEOUT; + } + else + { + --(*cp); + LG_DBG(LG_DEBUG_ALWAYS, "more than %d alerts", LG_MAX_ALERTS); + } + } + } + + if (ep != NULL) + { + p->last_evt_ts = ts; + p->last_evt_lv = 2 - ep->id; /* (falling) 2 (rising) 1 -> 0 1 */ + p->debounced = 0; + + if (!p->debounce_nanos) // report straightaway if no debounce + { + + aBuf[*cp].report.timestamp = p->last_evt_ts; + aBuf[*cp].report.level = p->last_evt_lv; + aBuf[*cp].report.chip = p->chip->gpiochip; + aBuf[*cp].report.gpio = p->gpio; + aBuf[*cp].report.flags = 0; + aBuf[*cp].nfyHandle = p->nfyHandle; + + if (++(*cp) < LG_MAX_ALERTS) + { + p->watchdogd = 0; + p->last_rpt_ts = p->last_evt_ts; + p->last_rpt_lv = p->last_evt_lv; + } + else + { + --(*cp); + LG_DBG(LG_DEBUG_ALWAYS, "more than %d alerts", LG_MAX_ALERTS); + } + } + } +} + +void *lgPthAlert(void) +{ + lgAlertRec_p p, t; + int i, e; + int num_gpio; + int gpiobasecount; + int retval; + int count=0; + int sent; + int bytes; + uint64_t tmax; + struct pollfd pfd[64]; + lgAlertRec_p pAlertRec[64]; + struct gpioevent_data eIn[LG_GPIO_MAX_ALERTS_PER_READ]; + struct timespec tspec = {0, 5e5}; /* 0.5 ms timeout */ + + while (1) + { + pthread_mutex_lock(&lgAlertMutex); + + if (alertRec != NULL) + { + p = alertRec; + i = 0; + + /* poll active alerts */ + + while (p != NULL) + { + if (p->active) + { + pfd[i].fd= p->state->fd; + pfd[i].events = POLLIN|POLLPRI; + pAlertRec[i] = p; + i++; + } + else + { + /* delete inactive record */ + + if (p->prev) p->prev->next = p->next; + else alertRec = p->next; + + if (p->next) p->next->prev = p->prev; + + t = p; p = p->prev; free(t); + } + + if (p) p = p->next; + } + + num_gpio = i; + + pthread_mutex_unlock(&lgAlertMutex); + + if (num_gpio > 0) + { + tmax = -1; /* largest value as int */ + + retval = ppoll(pfd, num_gpio, &tspec, NULL); + + xNowTimestamp = lguTimestamp(); + + for (i=0; i 0) && (pfd[i].revents)) + { + /* GPIO changed during ppoll */ + + bytes = read(pfd[i].fd, &eIn, sizeof(eIn)); + + if (bytes > 0) + { + e = 0; + + while (bytes >= sizeof(eIn[0])) + { + /* debounce and watchdog */ + xDebWatEvt(p, eIn[e].timestamp, &count, &eIn[e]); + + bytes -= sizeof(eIn[0]); + + e++; + } + + if (e) + { + p->last_rpt_ts = eIn[e-1].timestamp; + + if (eIn[e-1].timestamp < tmax) + { + tmax = eIn[e-1].timestamp; + } + } + + if (bytes) + { + if (p->active) + LG_DBG(LG_DEBUG_ALWAYS, "bytes left=%d (%s)", + bytes, strerror(errno)); + } + } + else + { + if (p->active) + LG_DBG(LG_DEBUG_ALWAYS, "read error %d (%s)", + errno, strerror(errno)); + } + } + else + { + /* GPIO not changed during ppoll */ + + xDebWatEvt(p, xNowTimestamp, &count, NULL); + } + + if (gpiobasecount < count) + { + if (p->state->alertFunc) + { + (p->state->alertFunc)(count-gpiobasecount, + &aBuf[gpiobasecount], p->state->userdata); + } + } + + } + + + if (count > 1) + { + // printbuf(count, "pre qsort"); + qsort(aBuf, count, sizeof(aBuf[0]), tscomp); + lgcheck(count, "check post qsort"); + // printbuf(count, "post qsort"); + } + + /* emit any due alerts */ + + // printbuf(count, "pre emit"); + sent = emit(count, tmax); + if (sent) + { + if (sent != count) + { + /* shuffle entries down */ + memmove(aBuf, aBuf+sent, sizeof(aBuf[0])*(count-sent)); + } + count -= sent; + } + //printbuf(count, "post emit"); + } + else /* no active alerts */ + { + emit(count, -1); /* empty the buffer */ + count = 0; + + xWaitForSignal(&lgAlertCond, &lgAlertCondMutex); + } + } + else /* no alerts */ + { + pthread_mutex_unlock(&lgAlertMutex); + + emit(count, -1); /* empty the buffer */ + count = 0; + + xWaitForSignal(&lgAlertCond, &lgAlertCondMutex); + } + } + + pthAlertRunning = LG_THREAD_NONE; + + pthread_exit(NULL); +} + +void lgPthAlertStart(void) +{ + if (!pthAlertRunning) + { + if (pthread_create(&pthAlert, NULL, (void*)lgPthAlert, NULL) == 0) + { + pthread_detach(pthAlert); + pthAlertRunning = LG_THREAD_STARTED; + } + } +} + +void lgPthAlertStop(lgChipObj_p chip) +{ + lgAlertRec_p evt; + + /* stop any alert reads on chip */ + + for (evt=alertRec; evt!=NULL; evt=evt->next) + { + if (chip->handle == evt->chip->handle) evt->active =0; + } + + xSendUnwaitSignal(&lgAlertCond, &lgAlertCondMutex); +} + +lgAlertRec_p lgGpioGetAlertRec(lgChipObj_p chip, int gpio) +{ + lgAlertRec_p p = alertRec; + + while ((p != NULL) && ((p->chip != chip) || (p->gpio != gpio))) + p = p->next; + return p; +} + +lgAlertRec_p lgGpioCreateAlertRec( + lgChipObj_p chip, int gpio, lgLineInf_p state, int nfyHandle) +{ + lgAlertRec_p p; + + p = malloc(sizeof(lgAlertRec_t)); + + if (p) + { + p->chip = chip; + p->gpio = gpio; + p->state = state; + p->nfyHandle = nfyHandle; + p->active = 1; + p->debounced = 1; + p->watchdogd = 1; + p->last_rpt_lv = -1; /* impossible level */ + p->debounce_nanos = state->debounce_us * 1e3; + p->watchdog_nanos = state->watchdog_us * 1e3; + p->eFlags = state->eFlags; + pthread_mutex_lock(&lgAlertMutex); + + p->prev = NULL; + p->next = alertRec; + if (alertRec) alertRec->prev = p; + alertRec = p; + + pthread_mutex_unlock(&lgAlertMutex); + + xSendUnwaitSignal(&lgAlertCond, &lgAlertCondMutex); + } + return p; +} + + diff --git a/lgPthAlerts.h b/lgPthAlerts.h new file mode 100644 index 0000000..095cb7d --- /dev/null +++ b/lgPthAlerts.h @@ -0,0 +1,66 @@ +/* +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 +*/ + +#ifndef LG_PTH_ALERTS_H +#define LG_PTH_ALERTS_H + +#include + +#include "lgpio.h" +#include "lgGpio.h" + +typedef struct lgAlertRec_s +{ + uint64_t last_rpt_ts; + uint64_t last_evt_ts; + uint64_t debounce_nanos; + uint64_t watchdog_nanos; + int last_evt_lv; + int last_rpt_lv; + int debounced; + int watchdogd; + int eFlags; + int gpio; + int nfyHandle; + lgLineInf_p state; + int active; + lgChipObj_p chip; + struct lgAlertRec_s *prev; + struct lgAlertRec_s *next; +} lgAlertRec_t, *lgAlertRec_p; + +lgAlertRec_p lgGpioGetAlertRec(lgChipObj_p chip, int gpio); + +lgAlertRec_p lgGpioCreateAlertRec( + lgChipObj_p chip, int gpio, lgLineInf_p state, int nfyHandle); + +void *lgPthAlert(void); +void lgPthAlertStart(void); +void lgPthAlertStop(lgChipObj_p chip); + +#endif + diff --git a/lgPthSocket.c b/lgPthSocket.c new file mode 100644 index 0000000..070f827 --- /dev/null +++ b/lgPthSocket.c @@ -0,0 +1,231 @@ +/* +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 +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include "lgpio.h" +#include "rgpiod.h" + +#include "lgCtx.h" +#include "lgDbg.h" +#include "lgHdl.h" + +static void *xSocketThreadHandler(void *fdC) +{ + int sock = *(int*)fdC; + int opt; + lgCtx_p Ctx; + lgCmd_t cmdBuf[CMD_MAX_EXTENSION/sizeof(lgCmd_t)]; + lgCmd_p cmdP=cmdBuf; + uint32_t *arg=(uint32_t*)&cmdP[1]; + + free(fdC); + + Ctx = lgCtxGet(); + + if (!Ctx) return 0; + + /* Disable the Nagle algorithm. */ + opt = 1; + + setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char*)&opt, sizeof(int)); + + while (1) + { + if (recv(sock, cmdP, sizeof(lgCmd_t), MSG_WAITALL) != + sizeof(lgCmd_t)) break; + + LG_DBG(LG_DEBUG_INTERNAL, "magic=%d size=%d cmd=%d Q=%d I=%d H=%d", + cmdP->magic, cmdP->size, cmdP->cmd, + cmdP->doubles, cmdP->longs, cmdP->shorts); + + if (cmdP->size) + { + if (cmdP->size < (sizeof(cmdBuf)-sizeof(lgCmd_t))) + { + /* read extension into buf */ + if (recv(sock, &cmdBuf[1], cmdP->size, MSG_WAITALL) != cmdP->size) + { + /* Serious error. No point continuing. */ + + LG_DBG(LG_DEBUG_ALWAYS, + "recv failed for %"PRId32" bytes, sock=%d", + cmdP->size, sock); + + break; + } + } + else + { + /* Serious error. No point continuing. */ + + LG_DBG(LG_DEBUG_ALWAYS, + "message too large %"PRId32"(%zd), sock=%d", + cmdP->size, sizeof(cmdBuf)-sizeof(lgCmd_t), sock); + + break; + } + } + + if (cmdP->cmd == LG_CMD_NOIB) + { + /* Enable the Nagle algorithm. */ + opt = 0; + setsockopt( + sock, IPPROTO_TCP, TCP_NODELAY, (char*)&opt, sizeof(int)); + + /* set sock as the argument */ + arg[0] = sock; + } + + cmdP->status = lgExecCmd(cmdBuf, sizeof(cmdBuf)); + + LG_DBG(LG_DEBUG_INTERNAL, "status=%d size=%d cmd=%d Q=%d I=%d H=%d", + cmdP->status, cmdP->size, cmdP->cmd, + cmdP->doubles, cmdP->longs, cmdP->shorts); + + if (write(sock, cmdBuf, sizeof(lgCmd_t))) ; /* ignore errors */ + + if (cmdP->size) + { + if (write(sock, &cmdBuf[1], cmdP->size)); /* ignore errors */ + } + } + + //lgNotifyCloseOrphans(-1, sock); + + lgHdlPurgeByOwner(Ctx->owner); + + close(sock); + + LG_DBG(LG_DEBUG_INTERNAL, "Socket %d closed", sock); + + LG_DBG(LG_DEBUG_INTERNAL, "free context memory %d", Ctx->owner); + + free(Ctx); + + return 0; +} + +static int xAddrAllowed(struct sockaddr *saddr) +{ + int i; + uint32_t addr; + + if (!gNumSockNetAddr) return 1; + + // FIXME: add IPv6 whitelisting support + if (saddr->sa_family != AF_INET) return 0; + + addr = ((struct sockaddr_in *) saddr)->sin_addr.s_addr; + + for (i=0; i= 0) + { + pthread_t thr; + + fdC = accept(gFdSock, (struct sockaddr *)&client, (socklen_t*)&c); + + lgNotifyCloseOrphans(-1, fdC); + + if (xAddrAllowed((struct sockaddr *)&client)) + { + LG_DBG(LG_DEBUG_INTERNAL, "Connection accepted on socket %d", fdC); + + sock = malloc(sizeof(int)); + + *sock = fdC; + + /* Enable tcp_keepalive */ + int optval = 1; + socklen_t optlen = sizeof(optval); + + if (setsockopt(fdC, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen) < 0) + { + LG_DBG(LG_DEBUG_ALWAYS, "setsockopt() fail, closing socket %d", fdC); + close(fdC); + } + + LG_DBG(LG_DEBUG_INTERNAL, "SO_KEEPALIVE enabled on socket %d\n", fdC); + + if (pthread_create + (&thr, &attr, xSocketThreadHandler, (void*) sock) < 0) + PARAM_ERROR((void*)LG_INIT_FAILED, + "socket pthread_create failed (%m)"); + } + else + { + LG_DBG(LG_DEBUG_ALWAYS, "Connection rejected, closing"); + close(fdC); + } + } + + if (fdC < 0) + PARAM_ERROR((void*)LG_INIT_FAILED, "accept failed (%m)"); + + return 0; +} + diff --git a/lgPthTx.c b/lgPthTx.c new file mode 100644 index 0000000..99c48b2 --- /dev/null +++ b/lgPthTx.c @@ -0,0 +1,319 @@ +/* +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 +*/ + +#include + +#include "lgDbg.h" +#include "lgHdl.h" +#include "lgPthTx.h" + +int lgMinTxDelay = 10; + +static pthread_t pthTx; +static pthread_mutex_t lgTxMutex = PTHREAD_MUTEX_INITIALIZER; +static volatile lgTxRec_p txRec = NULL; +static int pthTxRunning = LG_THREAD_NONE; +static struct timespec pthTxReq; +static int pthTxDelayMicros = 0; + +void *lgPthTx(void) +{ + lgTxRec_p p, t; + int i; + + clock_gettime(CLOCK_MONOTONIC, &pthTxReq); // get current time + + while (1) + { + lgPthTxLock(); + + // update times + + for (p=txRec; p!=NULL; p=p->next) + p->next_micros -= pthTxDelayMicros; + + // output changed edges and calculate next delay + + pthTxDelayMicros = 20000; // run at least fifty times a second + + p = txRec; + + while (p != NULL) + { + if (p->active) + { + if (p->next_micros <= 0) + { + if (p->type == LG_TX_PWM) + { + if (p->next_level || (p->micros_on[0] == 0)) + { + /* start of cycle */ + + if ((p->cycles[0] <= 0) && (p->entries > 1)) + { + for (i=0; ientries; i++) + { + p->micros_on[i] = p->micros_on[i+1]; + p->micros_off[i] = p->micros_off[i+1]; + p->cycles[i] = p->cycles[i+1]; + } + --p->entries; + } + + if (p->cycles[0] == 0) /* 0 is a result of countdown */ + { + xWrite(p->chip, p->gpio, 0); + p->active = 0; + } + else if (p->micros_on[0]) + { + xWrite(p->chip, p->gpio, 1); + p->next_micros += p->micros_on[0]; + if (p->micros_off[0]) p->next_level = 0; + } + else + { + xWrite(p->chip, p->gpio, 0); + p->next_micros += p->micros_off[0]; + p->next_level = 1; + } + + if (--p->cycles[0] < 0) p->cycles[0] = -1; + } + else /* middle of cycle */ + { + xWrite(p->chip, p->gpio, 0); + p->next_micros += p->micros_off[0]; + p->next_level = 1; + } + } + else if (p->type == LG_TX_WAVE) + { + if (p->pulse_pos >= p->num_pulses[0]) + { + if (p->entries > 1) + { + for (i=0; ientries; i++) + { + p->pulses[i] = p->pulses[i+1]; + p->num_pulses[i] = p->num_pulses[i+1]; + } + --p->entries; + p->pulse_pos = 0; + } + } + + if (p->pulse_pos < p->num_pulses[0]) + { + xGroupWrite(p->chip, p->gpio, + p->pulses[0][p->pulse_pos].bits, + p->pulses[0][p->pulse_pos].mask); + p->next_micros += p->pulses[0][p->pulse_pos].delay; + (p->pulse_pos)++; + } + else p->active = 0; + } + } + + if (p->next_micros < pthTxDelayMicros) + pthTxDelayMicros = p->next_micros; + } + else + { + /* delete inactive record */ + + if (p->prev) p->prev->next = p->next; + else txRec = p->next; + + if (p->next) p->next->prev = p->prev; + + if (p->type == LG_TX_WAVE) + { + /* free the malloc'd pulses */ + for (i=0; ientries; i++) + { + free(p->pulses[i]); + p->pulses[i] = NULL; + } + } + + t = p; p = p->prev; free(t); + } + + if (p) p = p->next; + } + + pthTxReq.tv_nsec += (pthTxDelayMicros * 1000); + if (pthTxReq.tv_nsec >= 1000000000) + { + pthTxReq.tv_sec += 1; + pthTxReq.tv_nsec -= 1000000000; + } + + lgPthTxUnlock(); + + // sleep until next edge + + while (clock_nanosleep( + CLOCK_MONOTONIC, TIMER_ABSTIME, &pthTxReq, NULL)); + } + + pthTxRunning = LG_THREAD_NONE; + pthread_exit(NULL); +} + +void lgPthTxStart(void) +{ + if (!pthTxRunning) + { + if (pthread_create(&pthTx, NULL, (void*)lgPthTx, NULL) == 0) + { + pthread_detach(pthTx); + pthTxRunning = LG_THREAD_STARTED; + } + } +} + +void lgPthTxLock(void) +{ + pthread_mutex_lock(&lgTxMutex); +} + +void lgPthTxUnlock(void) +{ + pthread_mutex_unlock(&lgTxMutex); +} + +void lgPthTxStop(lgChipObj_p chip) +{ + lgTxRec_p pwm; + + /* stop any PWM on chip */ + + for (pwm=txRec; pwm!=NULL; pwm=pwm->next) + { + if (chip->handle == pwm->chip->handle) pwm->active =0; + } +} + +lgTxRec_p lgGpioGetTxRec(lgChipObj_p chip, int gpio, int type) +{ + lgTxRec_p p; + + p = txRec; + + while ((p != NULL) && + ((p->chip != chip) || (p->gpio != gpio) || (p->type !=type))) + p = p->next; + + return p; +} + +lgTxRec_p lgGpioCreateTxRec( + lgChipObj_p chip, + int gpio, + int micros_on, + int micros_off, + int micros_offset, + int cycles) +{ + lgTxRec_p p; + int usec, ct, cyc, frac, left; + + p = malloc(sizeof(lgTxRec_t)); + + if (p) + { + p->type = LG_TX_PWM; + p->chip = chip; + p->gpio = gpio; + p->entries = 1; + p->micros_on[0] = micros_on; + p->micros_off[0] = micros_off; + p->micros_offset = micros_offset; + if (cycles) p->cycles[0] = cycles; else p->cycles[0] = -1; + if (micros_on) p->next_level = 1; else p->next_level = 0; + p->active = 1; + + lgPthTxLock(); + + usec = pthTxReq.tv_nsec / 1000; + ct = micros_on + micros_off; + cyc = usec / ct; + frac = usec - (ct *cyc); + left = ct - frac + pthTxDelayMicros + micros_offset; + p->next_micros = left; + + p->prev = NULL; + p->next = txRec; + if (txRec) txRec->prev = p; + txRec = p; + + lgPthTxUnlock(); + } + + return p; +} + +lgTxRec_p lgGroupCreateWaveRec( + lgChipObj_p chip, + int gpio, + int count, + lgPulse_p pulses) +{ + lgTxRec_p p; + + p = malloc(sizeof(lgTxRec_t)); + + if (p) + { + p->type = LG_TX_WAVE; + p->chip = chip; + p->gpio = gpio; + p->entries = 1; + p->active = 1; + + p->pulses[0] = pulses; + p->num_pulses[0] = count; + p->pulse_pos = 0; + + lgPthTxLock(); + + p->next_micros = pthTxDelayMicros; + + p->prev = NULL; + p->next = txRec; + if (txRec) txRec->prev = p; + txRec = p; + + lgPthTxUnlock(); + } + + return p; +} + diff --git a/lgPthTx.h b/lgPthTx.h new file mode 100644 index 0000000..1124566 --- /dev/null +++ b/lgPthTx.h @@ -0,0 +1,86 @@ +/* +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 +*/ + +#ifndef LG_PTH_PWM_H +#define LG_PTH_PWM_H + +#include + +#include "lgpio.h" +#include "lgGpio.h" + +#define LG_TX_BUF 10 + +typedef struct lgTxRec_s +{ + int active; + struct lgTxRec_s *prev; + struct lgTxRec_s *next; + int next_micros; + lgChipObj_p chip; + int gpio; + int entries; /* number of entries in LG_TX_BUF arrays */ + int type; /* PWM or WAVE */ + union + { + struct + { + int micros_on[LG_TX_BUF]; + int micros_off[LG_TX_BUF]; + int cycles[LG_TX_BUF]; + int micros_offset; // start offset micros into cycle + int next_level; + }; + struct + { + lgPulse_p pulses[LG_TX_BUF]; + int num_pulses[LG_TX_BUF]; + int pulse_pos; + }; + }; +} lgTxRec_t, *lgTxRec_p; + +lgTxRec_p lgGpioGetTxRec(lgChipObj_p chip, int gpio, int type); + +lgTxRec_p lgGpioCreateTxRec( + lgChipObj_p chip, + int gpio, + int micros_on, + int micros_off, + int micros_offset, + int cycles); + +lgTxRec_p lgGroupCreateWaveRec( + lgChipObj_p chip, int gpio, int count, lgPulse_p pulses); + +void lgPthTxStart(void); +void lgPthTxStop(lgChipObj_p chip); +void lgPthTxLock(void); +void lgPthTxUnlock(void); + +#endif + diff --git a/lgSPI.c b/lgSPI.c new file mode 100644 index 0000000..61c08b2 --- /dev/null +++ b/lgSPI.c @@ -0,0 +1,218 @@ +/* +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 +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lgpio.h" + +#include "lgDbg.h" +#include "lgHdl.h" + +typedef struct +{ + int speed; + int fd; + uint32_t flags; +} lgSpiObj_t, *lgSpiObj_p; + +static int xSpiXfer( + int fd, int speed, const char *txBuf, char *rxBuf, int count) +{ + int err; + struct spi_ioc_transfer spi; + + memset(&spi, 0, sizeof(spi)); + + spi.tx_buf = (uintptr_t)txBuf; + spi.rx_buf = (uintptr_t)rxBuf; + spi.len = count; + spi.speed_hz = speed; + spi.delay_usecs = 0; + spi.bits_per_word = 8; + spi.cs_change = 0; + + err = ioctl(fd, SPI_IOC_MESSAGE(1), &spi); + + return err; +} + +static void _lgSpiClose(lgSpiObj_p spi) +{ + if (spi) close(spi->fd); +} + + +int lgSpiOpen( + int spiDev, int spiChan, int baud, int spiFlags) +{ + int handle; + lgSpiObj_p spi; + int fd; + char spiMode; + char spiBits = 8; + char dev[128]; + + LG_DBG(LG_DEBUG_TRACE, "spiDev=%d spiChan=%d baud=%d spiFlags=0x%X", + spiDev, spiChan, baud, spiFlags); + + spiMode = spiFlags & 3; + spiBits = 8; + + sprintf(dev, "/dev/spidev%d.%d", spiDev, spiChan); + + if ((fd = open(dev, O_RDWR)) < 0) + { + return LG_SPI_OPEN_FAILED; + } + + if (ioctl(fd, SPI_IOC_WR_MODE, &spiMode) < 0) + { + close(fd); + return LG_SPI_IOCTL_FAILED; + } + + if (ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &spiBits) < 0) + { + close(fd); + return LG_SPI_IOCTL_FAILED; + } + + if (ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &baud) < 0) + { + close(fd); + return LG_SPI_IOCTL_FAILED; + } + + handle = lgHdlAlloc( + LG_HDL_TYPE_SPI, sizeof(lgSpiObj_t), (void **)&spi, _lgSpiClose); + + if (handle < 0) + { + PARAM_ERROR(LG_NO_HANDLE, "no free handles"); + } + + spi->fd = fd; + spi->speed = baud; + spi->flags = spiFlags; + + return handle; +} + +int lgSpiClose(int handle) +{ + int status; + lgSpiObj_p spi; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d", handle); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_SPI, (void **)&spi); + + if (status == LG_OKAY) + { + status = lgHdlFree(handle, LG_HDL_TYPE_SPI); + lgHdlUnlock(handle); + } + + return status; +} + +int lgSpiRead(int handle, char *rxBuf, int count) +{ + int status; + lgSpiObj_p spi; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d count=%d [%s]", + handle, count, lgDbgBuf2Str(count, rxBuf)); + + if (((unsigned)count > LG_MAX_SPI_DEVICE_COUNT) || !count) + PARAM_ERROR(LG_BAD_SPI_COUNT, "bad count (%d)", count); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_SPI, (void **)&spi); + + if (status == LG_OKAY) + { + status = xSpiXfer(spi->fd, spi->speed, NULL, rxBuf, count); + + lgHdlUnlock(handle); + } + + return status; +} + +int lgSpiWrite(int handle, const char *txBuf, int count) +{ + int status; + lgSpiObj_p spi; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d count=%d [%s]", + handle, count, lgDbgBuf2Str(count, txBuf)); + + if (((unsigned)count > LG_MAX_SPI_DEVICE_COUNT) || !count) + PARAM_ERROR(LG_BAD_SPI_COUNT, "bad count (%d)", count); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_SPI, (void **)&spi); + + if (status == LG_OKAY) + { + status = xSpiXfer(spi->fd, spi->speed, txBuf, NULL, count); + + lgHdlUnlock(handle); + } + + return status; +} + +int lgSpiXfer(int handle, const char *txBuf, char *rxBuf, int count) +{ + int status; + lgSpiObj_p spi; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d count=%d [%s]", + handle, count, lgDbgBuf2Str(count, txBuf)); + + if (((unsigned)count > LG_MAX_SPI_DEVICE_COUNT) || !count) + PARAM_ERROR(LG_BAD_SPI_COUNT, "bad count (%d)", count); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_SPI, (void **)&spi); + + if (status == LG_OKAY) + { + status = xSpiXfer(spi->fd, spi->speed, txBuf, rxBuf, count); + + lgHdlUnlock(handle); + } + + return status; +} + diff --git a/lgScript.c b/lgScript.c new file mode 100644 index 0000000..44a5dc6 --- /dev/null +++ b/lgScript.c @@ -0,0 +1,784 @@ +/* +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 +*/ + +#include +#include +#include +#include + +#include "lgpio.h" +#include "rgpiod.h" + +#include "lgCmd.h" +#include "lgCtx.h" +#include "lgDbg.h" +#include "lgHdl.h" + +#define LG_SCRIPT_HALT 0 +#define LG_SCRIPT_RUN 1 +#define LG_SCRIPT_DELETE 2 + +#define LG_SCRIPT_STACK_SIZE 256 + +typedef struct +{ + int id; + int request; + int run_state; + pthread_t *pthIdp; + pthread_mutex_t pthMutex; + pthread_cond_t pthCond; + cmdScript_t script; + char user[LG_USER_LEN]; + int share; +} lgScript_t, *lgScript_p; + + +static void _scriptClose(lgScript_p s) +{ + LG_DBG(LG_DEBUG_ALWAYS, "objp=*%p", s); + + if (s->script.par) free(s->script.par); + + s->script.par = NULL; +} + + +int myScriptNameValid(char *name) +{ + int i, c, len, valid; + + len = strlen(name); + + valid = 1; + + for (i=0; i 0) + { + return S[--(*SP)]; + } + else + { + s->run_state = LG_SCRIPT_FAILED; + LG_DBG(LG_DEBUG_ALWAYS, "script %d too many pops", s->id); + return 0; + } +} + +/* ----------------------------------------------------------------------- */ + +static void scrPush(lgScript_p s, int *SP, int *S, int val) +{ + if ((*SP) < LG_SCRIPT_STACK_SIZE) + { + S[(*SP)++] = val; + } + else + { + s->run_state = LG_SCRIPT_FAILED; + LG_DBG(LG_DEBUG_ALWAYS, "script %d too many pushes", s->id); + } +} + +/* ----------------------------------------------------------------------- */ + +static void scrSwap(int *v1, int *v2) +{ + int t; + + t=*v1; *v1=*v2; *v2= t; +} + +/* ----------------------------------------------------------------------- */ + +int scrSys(char *cmd, uint32_t p0, uint32_t p1) +{ + char cmdBuf[1024]; + int status; + + if (!myScriptNameValid(cmd)) + PARAM_ERROR(LG_BAD_SCRIPT_NAME, "bad script name (%s)", cmd); + + snprintf(cmdBuf, sizeof(cmdBuf), "%s/cgi/%s %u %u", + lguGetWorkDir(), cmd, p0, p1); + + LG_DBG(LG_DEBUG_USER, "%s", cmdBuf); + + status = system(cmdBuf); + + if (status < 0) status = LG_BAD_SHELL_STATUS; + + return status; +} + +static uint32_t xrl(uint32_t x, unsigned int n) +{ + return (x << n) | (x >> (-n & 31)); +} + +static uint32_t xrr(uint32_t x, unsigned int n) +{ + return (x >> n) | (x << (-n & 31)); +} + +static uint32_t xsl(uint32_t x, unsigned int n) +{ + return (x << n); +} + +static uint32_t xsr(uint32_t x, unsigned int n) +{ + return (x >> n); +} + +/* ----------------------------------------------------------------------- */ + +static void *pthScript(void *x) +{ + lgScript_p s; + int i, t; + cmdInstr_t instr; + int32_t p0, p1, p0o, p1o, *t1, *t2; + int32_t PC, A, F, SP; + uint32_t tmp; + int S[LG_SCRIPT_STACK_SIZE]; + lgCtx_p Ctx; + lgCmd_t cmdBuf[CMD_MAX_EXTENSION/sizeof(lgCmd_t)]; + lgCmd_p cmdP=cmdBuf; + uint32_t *arg=(uint32_t*)&cmdP[1]; + + Ctx = lgCtxGet(); + + if (!Ctx) return 0; + + s = x; + + strncpy(Ctx->user, s->user, LG_USER_LEN); + Ctx->autoUseShare = s->share; + + s->run_state = LG_SCRIPT_READY; + + while ((volatile int)s->request != LG_SCRIPT_DELETE) + { + pthread_mutex_lock(&s->pthMutex); + if ((volatile int)s->request != LG_SCRIPT_DELETE) + pthread_cond_wait(&s->pthCond, &s->pthMutex); + s->run_state = LG_SCRIPT_RUNNING; + pthread_mutex_unlock(&s->pthMutex); + + A = 0; + F = 0; + PC = 0; + SP = 0; + + while (((volatile int)s->request == LG_SCRIPT_RUN ) && + (s->run_state == LG_SCRIPT_RUNNING)) + { + // get local copy of next instruction + + instr = s->script.instr[PC]; + + if (instr.cmd < LG_CMD_SCRIPT) + { + // parameter and variable substitution + + for (i=0; iscript.var[t]; + else if (instr.opt[i] == CMD_PAR) instr.arg[i] = s->script.par[t]; + } + + fprintf(stderr, "PC=%d cmd=%d p0=%d p1=%d p2=%d p3=%d\n", + PC, instr.cmd, + instr.arg[0], instr.arg[1], instr.arg[2], instr.arg[3]); + + fflush(stderr); + + cmdP->magic = LG_MAGIC; + cmdP->size = 0; + cmdP->cmd = instr.cmd; + cmdP->doubles = 0; + cmdP->longs = 0; + cmdP->shorts = 0; + + for (i=0; iscript.var[t]; + else if (instr.opt[i] == CMD_PAR) instr.arg[i] = s->script.par[t]; + } + + p0 = instr.arg[0]; + p1 = instr.arg[1]; + + fprintf(stderr, "PC=%d cmd=%d p0=%d p0o=%d p1=%d p1o=%d\n", + PC, instr.cmd, + instr.arg[0], p0o, instr.arg[1], p1o); + + fflush(stderr); + + switch (instr.cmd) + { + case LG_CMD_ADD: A+=p0; F=A; PC++; break; + + case LG_CMD_AND: A&=p0; F=A; PC++; break; + + case LG_CMD_CALL: scrPush(s, &SP, S, PC+1); PC = p0; break; + + case LG_CMD_CMP: F=A-p0; PC++; break; + + case LG_CMD_DCR: + if (instr.opt[0] == CMD_PAR) + {--s->script.par[p0o]; F=s->script.par[p0o];} + else + {--s->script.var[p0o]; F=s->script.var[p0o];} + PC++; + break; + + case LG_CMD_DCRA: --A; F=A; PC++; break; + + case LG_CMD_DIV: A/=p0; F=A; PC++; break; + + case LG_CMD_HALT: s->run_state = LG_SCRIPT_ENDED; break; + + case LG_CMD_INR: + if (instr.opt[0] == CMD_PAR) + {++s->script.par[p0o]; F=s->script.par[p0o];} + else + {++s->script.var[p0o]; F=s->script.var[p0o];} + PC++; + break; + + case LG_CMD_INRA: ++A; F=A; PC++; break; + + case LG_CMD_JLE: if (F<=0) PC=p0; else PC++; break; + + case LG_CMD_JLT: if (F<0) PC=p0; else PC++; break; + + case LG_CMD_JMP: PC=p0; break; + + case LG_CMD_JNZ: if (F) PC=p0; else PC++; break; + + case LG_CMD_JGE: if (F>=0) PC=p0; else PC++; break; + + case LG_CMD_JGT: if (F>0) PC=p0; else PC++; break; + + case LG_CMD_JZ: if (!F) PC=p0; else PC++; break; + + case LG_CMD_LD: + if (instr.opt[0] == CMD_PAR) s->script.par[p0o]=p1; + else s->script.var[p0o]=p1; + PC++; + break; + + case LG_CMD_LDA: A=p0; PC++; break; + + case LG_CMD_MLT: A*=p0; F=A; PC++; break; + + case LG_CMD_MOD: A%=p0; F=A; PC++; break; + + case LG_CMD_OR: A|=p0; F=A; PC++; break; + + case LG_CMD_POP: + if (instr.opt[0] == CMD_PAR) + s->script.par[p0o]=scrPop(s, &SP, S); + else + s->script.var[p0o]=scrPop(s, &SP, S); + PC++; + break; + + case LG_CMD_POPA: A=scrPop(s, &SP, S); PC++; break; + + case LG_CMD_PUSH: + if (instr.opt[0] == CMD_PAR) + scrPush(s, &SP, S, s->script.par[p0o]); + else + scrPush(s, &SP, S, s->script.var[p0o]); + PC++; + break; + + case LG_CMD_PUSHA: scrPush(s, &SP, S, A); PC++; break; + + case LG_CMD_RET: PC=scrPop(s, &SP, S); break; + + case LG_CMD_RL: + if (instr.opt[0] == CMD_PAR) + { + tmp = xrl(s->script.par[p0o], p1); + s->script.par[p0o] = tmp; + F=tmp; + } + else + { + tmp = xrl(s->script.var[p0o], p1); + s->script.var[p0o] = tmp; + F=tmp; + } + PC++; + break; + + case LG_CMD_RLA: A=xrl(A, p0); F=A; PC++; break; + + case LG_CMD_RR: + if (instr.opt[0] == CMD_PAR) + { + tmp = xrr(s->script.par[p0o], p1); + s->script.par[p0o] = tmp; + F=tmp; + } + else + { + tmp = xrr(s->script.var[p0o], p1); + s->script.var[p0o] = tmp; + F=tmp; + } + PC++; + break; + + case LG_CMD_RRA: A=xrr(A, p0); F=A; PC++; break; + + case LG_CMD_SHL: + if (instr.opt[0] == CMD_PAR) + { + tmp = xsl(s->script.par[p0o], p1); + s->script.par[p0o] = tmp; + F=tmp; + } + else + { + tmp = xsl(s->script.var[p0o], p1); + s->script.var[p0o] = tmp; + F=tmp; + } + PC++; + break; + + case LG_CMD_SHLA: A=xsl(A, p0); F=A; PC++; break; + + case LG_CMD_SHR: + if (instr.opt[0] == CMD_PAR) + { + tmp = xsr(s->script.par[p0o], p1); + s->script.par[p0o] = tmp; + F=tmp; + } + else + { + tmp = xsr(s->script.var[p0o], p1); + s->script.var[p0o] = tmp; + F=tmp; + } + PC++; + break; + + case LG_CMD_SHRA: A=xsr(A, p0); F=A; PC++; break; + + case LG_CMD_STA: + if (instr.opt[0] == CMD_PAR) s->script.par[p0o]=A; + else s->script.var[p0o]=A; + PC++; + break; + + case LG_CMD_SUB: A-=p0; F=A; PC++; break; + + case LG_CMD_SYS: + //A=scrSys((char*)instr.arg[4], A, 0); + F=A; + PC++; + break; + + case LG_CMD_X: + if (instr.opt[0] == CMD_PAR) t1 = &s->script.par[p0o]; + else t1 = &s->script.var[p0o]; + + if (instr.opt[1] == CMD_PAR) t2 = &s->script.par[p1o]; + else t2 = &s->script.var[p1o]; + + scrSwap(t1, t2); + PC++; + break; + + case LG_CMD_XA: + if (instr.opt[0] == CMD_PAR) + scrSwap(&s->script.par[p0o], &A); + else + scrSwap(&s->script.var[p0o], &A); + PC++; + break; + + case LG_CMD_XOR: A^=p0; F=A; PC++; break; + + } + } + + if (PC >= s->script.instrs) s->run_state = LG_SCRIPT_ENDED; + } + + if (((volatile int)s->request == LG_SCRIPT_HALT) || + ((volatile int)s->request == LG_SCRIPT_DELETE)) + s->run_state = LG_SCRIPT_HALTED; + } + + lgHdlPurgeByOwner(Ctx->owner); + + LG_DBG(LG_DEBUG_ALWAYS, "free context memory %d", Ctx->owner); + + free(Ctx); + + s->run_state = LG_SCRIPT_EXITED; + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +void lgRawDumpScript(int handle) +{ + int i; + lgScript_p s; + int status; + + LG_DBG(LG_DEBUG_USER, "handle=%d", handle); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_SCRIPT, (void **)&s); + + if (status == LG_OKAY) + { + for (i=0; iscript.par[i]); + } + + fprintf(stderr, "\n"); + + for (i=0; iscript.var[i]); + } + + fprintf(stderr, "\n"); + + for (i=0; iscript.instrs; i++) + { + fprintf(stderr, + "%d: cmd=%d [%d(%d), %d(%d), %d(%d), %d(%d)]\n", + i, + s->script.instr[i].cmd, + s->script.instr[i].arg[0], + s->script.instr[i].opt[0], + s->script.instr[i].arg[1], + s->script.instr[i].opt[1], + s->script.instr[i].arg[2], + s->script.instr[i].opt[2], + s->script.instr[i].arg[3], + s->script.instr[i].opt[3]); + } + + lgHdlUnlock(handle); + } +} + +int lgScriptStore(char *script) +{ + lgScript_p s; + lgCtx_p Ctx; + int handle; + int status; + + LG_DBG(LG_DEBUG_TRACE, "script=[%s]", script); + + handle = lgHdlAlloc( + LG_HDL_TYPE_SCRIPT, sizeof(lgScript_t), (void**)&s, _scriptClose); + + if (handle < 0) return LG_NO_MEMORY; + + status = cmdParseScript(script, &s->script, 0); + + if (status == 0) + { + /* set the owner's user and share */ + + Ctx = lgCtxGet(); + + if (Ctx) + { + strncpy(s->user, Ctx->user, LG_USER_LEN); + s->share = Ctx->autoUseShare; + } + + s->request = LG_SCRIPT_HALT; + s->run_state = LG_SCRIPT_INITING; + + pthread_cond_init(&s->pthCond, NULL); + pthread_mutex_init(&s->pthMutex, NULL); + + s->id = handle; + + s->pthIdp = lgThreadStart(pthScript, s); + + status = handle; + } + else lgHdlFree(handle, LG_HDL_TYPE_SCRIPT); + + return status; +} + + +/* ----------------------------------------------------------------------- */ + +int lgScriptRun(int handle, int count, uint32_t *scriptParam) +{ + int status=0; + int i; + lgScript_p s; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d count=%d scriptParam=%08"PRIXPTR, + handle, count, (uintptr_t)scriptParam); + + if ((unsigned)count > LG_MAX_SCRIPT_PARAMS) + PARAM_ERROR(LG_TOO_MANY_PARAM, "bad number of parameters(%d)", count); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_SCRIPT, (void **)&s); + + if (status == LG_OKAY) + { + pthread_mutex_lock(&s->pthMutex); + + if (s->run_state != LG_SCRIPT_INITING) + { + if (scriptParam != NULL) + { + for (i=0; iscript.par[i] = scriptParam[i]; + } + + s->request = LG_SCRIPT_RUN; + + pthread_cond_signal(&s->pthCond); + } + else + { + status = LG_SCRIPT_NOT_READY; + } + + pthread_mutex_unlock(&s->pthMutex); + + lgHdlUnlock(handle); + } + + return status; +} + + +/* ----------------------------------------------------------------------- */ + +int lgScriptUpdate(int handle, int count, uint32_t *scriptParam) +{ + int status; + int i; + lgScript_p s; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d count=%d scriptParam=%08"PRIXPTR, + handle, count, (uintptr_t)scriptParam); + + if ((unsigned)count > LG_MAX_SCRIPT_PARAMS) + PARAM_ERROR(LG_TOO_MANY_PARAM, "bad number of parameters(%d)", count); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_SCRIPT, (void **)&s); + + if (status == LG_OKAY) + { + if (scriptParam != NULL) + { + pthread_mutex_lock(&s->pthMutex); + + for (i=0; iscript.par[i] = scriptParam[i]; + + pthread_mutex_unlock(&s->pthMutex); + } + + lgHdlUnlock(handle); + } + + return status; +} + + +/* ----------------------------------------------------------------------- */ + +int lgScriptStatus(int handle, uint32_t *scriptParam) +{ + int status; + int i; + lgScript_p s; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d scriptParam=%08"PRIXPTR, + handle, (uintptr_t)scriptParam); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_SCRIPT, (void **)&s); + + if (status == LG_OKAY) + { + pthread_mutex_lock(&s->pthMutex); + + if (scriptParam != NULL) + { + for (i=0; iscript.par[i]; + } + + status = s->run_state; + + if (status >= LG_SCRIPT_ENDED) s->run_state = LG_SCRIPT_READY; + + pthread_mutex_unlock(&s->pthMutex); + + lgHdlUnlock(handle); + } + + return status; +} + + +/* ----------------------------------------------------------------------- */ + +int lgScriptStop(int handle) +{ + int status; + lgScript_p s; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d", handle); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_SCRIPT, (void **)&s); + + if (status == LG_OKAY) + { + pthread_mutex_lock(&s->pthMutex); + + s->request = LG_SCRIPT_HALT; + + pthread_cond_signal(&s->pthCond); + + pthread_mutex_unlock(&s->pthMutex); + + lgHdlUnlock(handle); + } + + return status; +} + +/* ----------------------------------------------------------------------- */ + +int lgScriptDelete(int handle) +{ + int status; + lgScript_p s; + pthread_t *pthIdp; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d", handle); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_SCRIPT, (void **)&s); + + if (status == LG_OKAY) + { + pthread_mutex_lock(&s->pthMutex); + + s->request = LG_SCRIPT_DELETE; + + pthread_cond_signal(&s->pthCond); + + pthread_mutex_unlock(&s->pthMutex); + + while (s->run_state != LG_SCRIPT_EXITED) + { + usleep(5000); /* give script time to halt */ + } + + pthIdp = s->pthIdp; + + status = lgHdlFree(handle, LG_HDL_TYPE_SCRIPT); + + lgHdlUnlock(handle); + + lgThreadStop(pthIdp); + } + + return status; +} + + +int lgShell(char *scriptName, char *scriptString) +{ + int status; + char cmdBuf[4096]; + + LG_DBG(LG_DEBUG_TRACE, "name=%s string=%s", scriptName, scriptString); + + if (!myScriptNameValid(scriptName)) + PARAM_ERROR(LG_BAD_SCRIPT_NAME, "bad script name (%s)", scriptName); + + snprintf(cmdBuf, sizeof(cmdBuf), + "%s/cgi/%s %s", lguGetConfigDir(), scriptName, scriptString); + + LG_DBG(LG_DEBUG_USER, "%s", cmdBuf); + + status = system(cmdBuf); + + if (status < 0) status = LG_BAD_SHELL_STATUS; + + return status; +} + diff --git a/lgSerial.c b/lgSerial.c new file mode 100644 index 0000000..1fdc3fd --- /dev/null +++ b/lgSerial.c @@ -0,0 +1,295 @@ +/* +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 +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lgpio.h" + +#include "lgDbg.h" +#include "lgHdl.h" + +typedef struct +{ + int16_t fd; + uint32_t flags; +} lgSerialObj_t, *lgSerialObj_p; + +static void _lgSerialClose(lgSerialObj_p ser) +{ + if (ser) close(ser->fd); +} + +int lgSerialOpen(const char *serDev, int serBaud, int serFlags) +{ + struct termios new; + int speed; + int fd; + int handle; + char serName[LG_MAX_PATH]; + lgSerialObj_p ser; + + LG_DBG(LG_DEBUG_TRACE, "serDev=%s serBaud=%d serFlags=0x%X", + serDev, serBaud, serFlags); + + switch (serBaud) + { + case 50: speed = B50; break; + case 75: speed = B75; break; + case 110: speed = B110; break; + case 134: speed = B134; break; + case 150: speed = B150; break; + case 200: speed = B200; break; + case 300: speed = B300; break; + case 600: speed = B600; break; + case 1200: speed = B1200; break; + case 1800: speed = B1800; break; + case 2400: speed = B2400; break; + case 4800: speed = B4800; break; + case 9600: speed = B9600; break; + case 19200: speed = B19200; break; + case 38400: speed = B38400; break; + case 57600: speed = B57600; break; + case 115200: speed = B115200; break; + case 230400: speed = B230400; break; + + default: + PARAM_ERROR(LG_BAD_SERIAL_SPEED, "bad speed (%d)", serBaud); + } + + if (serFlags) + PARAM_ERROR(LG_BAD_SERIAL_FLAGS, "bad serial flags (0x%X)", serFlags); + + snprintf(serName, LG_MAX_PATH, "/dev/%s", serDev); + + if ((fd = open(serName, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK)) == -1) + { + return LG_SERIAL_OPEN_FAILED; + } + + handle = lgHdlAlloc(LG_HDL_TYPE_SERIAL, sizeof(lgSerialObj_t), + (void**)&ser, _lgSerialClose); + + if (handle < 0) + { + close(fd); + return LG_NO_MEMORY; + } + + tcgetattr(fd, &new); + + cfmakeraw(&new); + + cfsetispeed(&new, speed); + cfsetospeed(&new, speed); + + new.c_cc [VMIN] = 0; + new.c_cc [VTIME] = 0; + + tcflush(fd, TCIFLUSH); + tcsetattr(fd, TCSANOW, &new); + + //fcntl(fd, F_SETFL, O_RDWR); + + ser->fd = fd; + ser->flags = serFlags; + + return handle; +} + +int lgSerialClose(int handle) +{ + int status; + lgSerialObj_p ser; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d", handle); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_SERIAL, (void **)&ser); + + if (status == LG_OKAY) + { + status = lgHdlFree(handle, LG_HDL_TYPE_SERIAL); + + lgHdlUnlock(handle); + } + + return status; +} + + +int lgSerialWriteByte(int handle, int bVal) +{ + char c; + lgSerialObj_p ser; + int status; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d bVal=%d", handle, bVal); + + if ((unsigned)bVal > 0xFF) + PARAM_ERROR(LG_BAD_SERIAL_PARAM, "bad parameter (%d)", bVal); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_SERIAL, (void **)&ser); + + if (status == LG_OKAY) + { + c = bVal; + + if (write(ser->fd, &c, 1) != 1) status = LG_SERIAL_WRITE_FAILED; + + lgHdlUnlock(handle); + } + + return status; +} + +int lgSerialReadByte(int handle) +{ + char x; + int r; + lgSerialObj_p ser; + int status; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d", handle); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_SERIAL, (void **)&ser); + + if (status == LG_OKAY) + { + r = read(ser->fd, &x, 1); + + if (r == 1) + status = ((int)x) & 0xFF; + else if (r == 0) + status = LG_SERIAL_READ_NO_DATA; + else if ((r == -1) && (errno == EAGAIN)) + status = LG_SERIAL_READ_NO_DATA; + else + status = LG_SERIAL_READ_FAILED; + + lgHdlUnlock(handle); + } + + return status; +} + +int lgSerialWrite(int handle, const char *txBuf, int count) +{ + int written=0, wrote=0; + lgSerialObj_p ser; + int status; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d count=%d [%s]", + handle, count, lgDbgBuf2Str(count, txBuf)); + + if (!count) + PARAM_ERROR(LG_BAD_SERIAL_PARAM, "bad count (%d)", count); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_SERIAL, (void **)&ser); + + if (status == LG_OKAY) + { + while ((written != count) && (wrote >= 0)) + { + wrote = write(ser->fd, txBuf+written, count-written); + + if (wrote >= 0) + { + written += wrote; + + if (written != count) usleep(50000); + } + } + + if (written != count) status = LG_SERIAL_WRITE_FAILED; + + lgHdlUnlock(handle); + } + + return status; +} + +int lgSerialRead(int handle, char *rxBuf, int count) +{ + int r; + lgSerialObj_p ser; + int status; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d count=%d rxBuf=%p", handle, count, rxBuf); + + if (!count) + PARAM_ERROR(LG_BAD_SERIAL_PARAM, "bad count (%d)", count); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_SERIAL, (void **)&ser); + + if (status == LG_OKAY) + { + r = read(ser->fd, rxBuf, count); + + if (r == -1) + { + if (errno == EAGAIN) + status = LG_SERIAL_READ_NO_DATA; + else + status = LG_SERIAL_READ_FAILED; + } + else + { + if (r < count) rxBuf[r] = 0; + status = r; + } + + lgHdlUnlock(handle); + } + + return status; +} + +int lgSerialDataAvailable(int handle) +{ + int status; + lgSerialObj_p ser; + + LG_DBG(LG_DEBUG_TRACE, "handle=%d", handle); + + status = lgHdlGetLockedObj(handle, LG_HDL_TYPE_SERIAL, (void **)&ser); + + if (status == LG_OKAY) + { + if (ioctl(ser->fd, FIONREAD, &status) == -1) status = 0; + + lgHdlUnlock(handle); + } + + return status; +} + diff --git a/lgThread.c b/lgThread.c new file mode 100644 index 0000000..86a1303 --- /dev/null +++ b/lgThread.c @@ -0,0 +1,88 @@ +/* +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 +*/ + +#include +#include + +#include "lgpio.h" + +#include "lgDbg.h" + +pthread_t *lgThreadStart(lgThreadFunc_t f, void *userdata) +{ + pthread_t *pth; + pthread_attr_t pthAttr; + + LG_DBG(LG_DEBUG_TRACE, "f=%08"PRIXPTR", userdata=%08"PRIXPTR, + (uintptr_t)f, (uintptr_t)userdata); + + pth = malloc(sizeof(pthread_t)); + + if (pth) + { + if (pthread_attr_init(&pthAttr)) + { + free(pth); + PARAM_ERROR(NULL, "pthread_attr_init failed"); + } + + if (pthread_attr_setstacksize(&pthAttr, STACK_SIZE)) + { + free(pth); + PARAM_ERROR(NULL, "pthread_attr_setstacksize failed"); + } + + if (pthread_create(pth, &pthAttr, f, userdata)) + { + free(pth); + PARAM_ERROR(NULL, "pthread_create failed"); + } + } + return pth; +} + + +void lgThreadStop(pthread_t *pth) +{ + LG_DBG(LG_DEBUG_TRACE, "pth=%08"PRIXPTR, (uintptr_t)pth); + + if (pth) + { + if (pthread_self() == *pth) + { + free(pth); + pthread_exit(NULL); + } + else + { + pthread_cancel(*pth); + pthread_join(*pth, NULL); + free(pth); + } + } +} + diff --git a/lgUtil.c b/lgUtil.c new file mode 100644 index 0000000..b4f1b94 --- /dev/null +++ b/lgUtil.c @@ -0,0 +1,244 @@ +/* +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 +*/ + +#include +#include +#include +#include +#include +#include + +#include "lgpio.h" + +#include "lgDbg.h" + +static char xConfigDir[LG_MAX_PATH]; +static char xWorkDir[LG_MAX_PATH]; + +uint64_t lguTimestamp(void) +{ + struct timespec xts; + + clock_gettime(CLOCK_REALTIME, &xts); // get current time + + return ((uint64_t)1E9 * xts.tv_sec) + xts.tv_nsec; +} + +double lguTime(void) +{ + return (double)lguTimestamp() / (double)1E9; +} + +void lguSleep(double sleepSecs) +{ + struct timespec ts, rem; + + if (sleepSecs > 0.0) + { + ts.tv_sec = sleepSecs; + ts.tv_nsec = (sleepSecs-(double)ts.tv_sec) * 1E9; + + while (clock_nanosleep(CLOCK_REALTIME, 0, &ts, &rem)) ts = rem; + } +} + +int lguSbcName(char *buf, int count) +{ + LG_DBG(LG_DEBUG_TRACE, ""); + + if ((buf == NULL) || (count < 1)) return 0; + + gethostname(buf, count); + buf[count-1] = 0; + + return strlen(buf) + 1; /* include null terminator in count */ +} + +int lguVersion(void) +{ + LG_DBG(LG_DEBUG_TRACE, ""); + + return LGPIO_VERSION; +} + +void xSetConfigDir(const char *dirStr) +{ + if (dirStr[0] == '/') // absolute path + { + if (strlen(dirStr) < LG_MAX_PATH) + strcpy(xConfigDir, dirStr); + else + strcpy(xConfigDir, "/tmp"); + } + else // relative to working directory + { + if (getcwd(xConfigDir, LG_MAX_PATH) != NULL) + { + // only append relative path if there is room + if ((strlen(xConfigDir) + strlen(dirStr) + 2) < LG_MAX_PATH) + { + strcat(xConfigDir, "/"); + strcat(xConfigDir, dirStr); + } + } + else strcpy(xConfigDir, "/tmp"); + } +} + +void lguSetConfigDir(const char *dirPath) +{ + LG_DBG(LG_DEBUG_TRACE, "dirPath=%s", dirPath); + + if (!xConfigDir[0]) + { + lguGetWorkDir(); // make sure working directory is known + + xSetConfigDir(dirPath); + } +} + +const char *lguGetConfigDir(void) +{ + const char *dirStr; + + if (!xConfigDir[0]) + { + lguGetWorkDir(); // make sure working directory is known + + dirStr = getenv(LG_CD); + + if (dirStr) xSetConfigDir(dirStr); + else + { + if (getcwd(xConfigDir, LG_MAX_PATH) == NULL) + strcpy(xConfigDir, "/tmp"); + } + } + + return xConfigDir; +} + +void xSetWorkDir(const char *dirStr) +{ + if (dirStr[0] == '/') // absolute path + { + if (strlen(dirStr) < LG_MAX_PATH) + strcpy(xWorkDir, dirStr); + else + strcpy(xWorkDir, "/tmp"); + } + else // relative to working directory + { + if (getcwd(xWorkDir, LG_MAX_PATH) != NULL) + { + if ((strlen(xWorkDir) + strlen(dirStr) + 2) < LG_MAX_PATH) + { + strcat(xWorkDir, "/"); + strcat(xWorkDir, dirStr); + } + } + else strcpy(xWorkDir, "/tmp"); + } +} + +void lguSetWorkDir(const char *dirPath) +{ + LG_DBG(LG_DEBUG_TRACE, "dirPath=%s", dirPath); + + if (!xWorkDir[0]) + { + xSetWorkDir(dirPath); + + if (chdir(xWorkDir) < 0) LG_DBG(LG_DEBUG_ALWAYS, + "can't set working directory (%s)", strerror(errno)); + } +} + +const char *lguGetWorkDir(void) +{ + const char *dirStr; + + if (!xWorkDir[0]) + { + dirStr = getenv(LG_WD); + + if (dirStr) xSetWorkDir(dirStr); + else + { + if (getcwd(xWorkDir, LG_MAX_PATH) == NULL) + strcpy(xWorkDir, "/tmp"); + } + + if (chdir(xWorkDir) < 0) LG_DBG(LG_DEBUG_ALWAYS, + "can't set working directory (%s)", strerror(errno)); + } + + return xWorkDir; +} + +int lguSetInternal(int cfgId, uint64_t cfgVal) +{ + LG_DBG(LG_DEBUG_TRACE, "Id=%d val=%"PRIu64"", cfgId, cfgVal); + + switch(cfgId) + { + case LG_CFG_ID_DEBUG_LEVEL: + lgDbgLevel = cfgVal | 1; + break; + + case LG_CFG_ID_MIN_DELAY: + if (cfgVal <= 1000) lgMinTxDelay = cfgVal; + else return LG_BAD_CONFIG_VALUE; + break; + + default: + return LG_BAD_CONFIG_ID; + } + return LG_OKAY; +} + +int lguGetInternal(int cfgId, uint64_t *cfgVal) +{ + LG_DBG(LG_DEBUG_TRACE, "Id=%d", cfgId); + + switch(cfgId) + { + case LG_CFG_ID_DEBUG_LEVEL: + *cfgVal = lgDbgLevel; + break; + + case LG_CFG_ID_MIN_DELAY: + *cfgVal = lgMinTxDelay; + break; + + default: + *cfgVal = 0; + return LG_BAD_CONFIG_ID; + } + return LG_OKAY; +} + diff --git a/lgpio.3 b/lgpio.3 new file mode 100644 index 0000000..80d19fd --- /dev/null +++ b/lgpio.3 @@ -0,0 +1,5184 @@ + +." Process this file with +." groff -man -Tascii lg.3 +." +.TH lgpio 3 2020-2020 Linux "lg archive" +.SH NAME +lgpio - A C library to manipulate a local SBC's GPIO. + +.SH SYNOPSIS + +#include + + +gcc -Wall -o prog prog.c -llgpio + + ./prog +.SH DESCRIPTION + + +.ad l + +.nh + +.br + +.br +lgpio is a C library for Linux Single Board Computers which +allows control of the General Purpose Input Output pins. + +.br + +.br +.SS Features +.br + +.br + +.br +o reading and writing GPIO singly and in groups + +.br +o software timed PWM and waves + +.br +o GPIO callbacks + +.br +o pipe notification of GPIO alerts + +.br +o I2C wrapper + +.br +o SPI wrapper + +.br +o serial link wrapper + +.br +o a simple interface to start and stop new threads + +.br + +.br +.SS Usage +.br + +.br +Include in your source files. + +.br + +.br +Assuming your source is in a single file called prog.c use the following +command to build and run the executable. + +.br + +.br + +.EX +gcc -Wall -o prog prog.c -llgpio +.br +./prog +.br + +.EE + +.br + +.br +For examples of usage see the C programs within the lg archive file. + +.br + +.br +.SS Notes +.br + +.br +All the functions which return an int return < 0 on error. + +.br + +.br + +.SH OVERVIEW + +.br +.SS GPIO +.br + +.br +lgGpiochipOpen Opens a gpiochip device +.br +lgGpiochipClose Closes a gpiochip device +.br + +.br +lgGpioGetChipInfo Gets gpiochip information +.br +lgGpioGetLineInfo Gets gpiochip line information +.br +lgGpioGetMode Gets the mode of a GPIO +.br + +.br +lgGpioSetUser Notifies Linux of the GPIO user +.br + +.br +lgGpioClaimInput Claims a GPIO for input +.br +lgGpioClaimOutput Claims a GPIO for output +.br +lgGpioClaimAlert Claims a GPIO for alerts +.br +lgGpioFree Frees a GPIO +.br + +.br +lgGroupClaimInput Claims a group of GPIO for inputs +.br +lgGroupClaimOutput Claims a group of GPIO for outputs +.br +lgGroupFree Frees a group of GPIO +.br + +.br +lgGpioRead Reads a GPIO +.br +lgGpioWrite Writes a GPIO +.br + +.br +lgGroupRead Reads a group of GPIO +.br +lgGroupWrite Writes a group of GPIO +.br + +.br +lgTxPulse Starts pulses on a GPIO +.br +lgTxPwm Starts PWM pulses on a GPIO +.br +lgTxServo Starts Servo pulses on a GPIO +.br +lgTxWave Starts a wave on a group of GPIO +.br +lgTxBusy See if tx is active on a GPIO or group +.br +lgTxRoom See if more room for tx on a GPIO or group +.br + +.br +lgGpioSetDebounce Sets the debounce time for a GPIO +.br +lgGpioSetWatchdog Sets the watchdog time for a GPIO +.br + +.br +lgGpioSetAlertsFunc Starts a GPIO callback +.br +lgGpioSetSamplesFunc Starts a GPIO callback for all GPIO +.br +.SS I2C +.br + +.br +lgI2cOpen Opens an I2C device +.br +lgI2cClose Closes an I2C device +.br + +.br +lgI2cWriteQuick SMBus write quick +.br + +.br +lgI2cReadByte SMBus read byte +.br +lgI2cWriteByte SMBus write byte +.br + +.br +lgI2cReadByteData SMBus read byte data +.br +lgI2cWriteByteData SMBus write byte data +.br + +.br +lgI2cReadWordData SMBus read word data +.br +lgI2cWriteWordData SMBus write word data +.br + +.br +lgI2cReadBlockData SMBus read block data +.br +lgI2cWriteBlockData SMBus write block data +.br + +.br +lgI2cReadI2CBlockData SMBus read I2C block data +.br +lgI2cWriteI2CBlockData SMBus write I2C block data +.br + +.br +lgI2cReadDevice Reads the raw I2C device +.br +lgI2cWriteDevice Writes the raw I2C device +.br + +.br +lgI2cProcessCall SMBus process call +.br +lgI2cBlockProcessCall SMBus block process call +.br + +.br +lgI2cSegments Performs multiple I2C transactions +.br +lgI2cZip Performs multiple I2C transactions +.br +.SS NOTIFICATIONS +.br + +.br +lgNotifyOpen Request a notification +.br +lgNotifyClose Close a notification +.br +lgNotifyPause Pause notifications +.br +lgNotifyResume Start notifications +.br +.SS SERIAL +.br + +.br +lgSerialOpen Opens a serial device +.br +lgSerialClose Closes a serial device +.br + +.br +lgSerialReadByte Reads a byte from a serial device +.br +lgSerialWriteByte Writes a byte to a serial device +.br + +.br +lgSerialRead Reads bytes from a serial device +.br +lgSerialWrite Writes bytes to a serial device +.br + +.br +lgSerialDataAvailable Returns number of bytes ready to be read +.br +.SS SPI +.br + +.br +lgSpiOpen Opens a SPI device +.br +lgSpiClose Closes a SPI device +.br + +.br +lgSpiRead Reads bytes from a SPI device +.br +lgSpiWrite Writes bytes to a SPI device +.br + +.br +lgSpiXfer Transfers bytes with a SPI device +.br +.SS THREADS +.br + +.br +lgThreadStart Start a new thread +.br +lgThreadStop Stop a previously started thread +.br +.SS UTILITIES +.br + +.br +lguVersion Gets the library version +.br +lguSbcName Gets the host name of the SBC +.br + +.br +lguGetInternal Get an internal configuration value +.br +lguSetInternal Set an internal configuration value +.br + +.br +lguSleep Sleeps for a given time +.br + +.br +lguTimestamp Gets the current timestamp +.br +lguTime Gets the current time +.br + +.br +lgErrStr Gets a text description of an error code +.br + +.br +lguSetWorkDir Set the working directory +.br +lguGetWorkDir Get the working directory +.br +.SH FUNCTIONS + +.IP "\fBint lgGpiochipOpen(int gpioDev)\fP" +.IP "" 4 +This returns a handle to a gpiochip device. + +.br + +.br + +.EX +gpioDev: >= 0 +.br + +.EE + +.br + +.br +If OK returns a handle (>= 0). + +.br + +.br +On failure returns a negative error code. + +.br + +.br +\fBExample\fP +.br + +.EX +h = lgGpiochipOpen(0); // open /dev/gpiochip0 +.br + +.br +if (h >= 0) +.br +{ +.br + // open ok +.br +} +.br +else +.br +{ +.br + // open error +.br +} +.br + +.EE + +.IP "\fBint lgGpiochipClose(int handle)\fP" +.IP "" 4 +This closes an opened gpiochip device. + +.br + +.br + +.EX +handle: >= 0 (as returned by \fBlgGpiochipOpen\fP) +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +\fBExample\fP +.br + +.EX +status = lgGpiochipClose(h); // close gpiochip +.br + +.br +if (status < 0) +.br +{ +.br + // close failed +.br +} +.br + +.EE + +.IP "\fBint lgGpioGetChipInfo(int handle, lgChipInfo_p chipInfo)\fP" +.IP "" 4 +This returns information about a gpiochip. + +.br + +.br + +.EX + handle: >= 0 (as returned by \fBlgGpiochipOpen\fP) +.br +chipInfo: A pointer to space for a lgChipInfo_t object +.br + +.EE + +.br + +.br +If OK returns 0 and updates chipInfo. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +This command gets the number of GPIO on the gpiochip, +its name, and its usage. + +.br + +.br +\fBExample\fP +.br + +.EX +lgChipInfo_t cInfo; +.br + +.br +status = lgGpioGetChipInfo(h, &cInfo); +.br + +.br +if (status == LG_OKAY) +.br +{ +.br + printf("lines=%d name=%s label=%s\n", +.br + cInfo.lines, cInfo.name, cInfo.label)) +.br +} +.br + +.EE + +.IP "\fBint lgGpioGetLineInfo(int handle, int gpio, lgLineInfo_p lineInfo)\fP" +.IP "" 4 +Returns information about a GPIO. + +.br + +.br + +.EX + handle: >= 0 (as returned by \fBlgGpiochipOpen\fP) +.br + gpio: >= 0, as legal for the gpiochip +.br +lineInfo: A pointer to space for a lgLineInfo_t object +.br + +.EE + +.br + +.br +If OK returns 0 and updates lineInfo. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +This command gets information for a GPIO of a gpiochip. +In particular it gets the GPIO number, kernel usage flags, +its user, and its purpose. + +.br + +.br +The usage flags are bits. + +.br + +.br +Bit value Bit meaning +.br +0 1 GPIO in use by the kernel +.br +1 2 GPIO is an output +.br +2 4 GPIO is active low +.br +3 8 GPIO is open drain +.br +4 16 GPIO is open source +.br + +.br + +.br +The user and purpose fields are filled in by the software which has +claimed the GPIO and may be blank. + +.br + +.br +\fBExample\fP +.br + +.EX +lgLineInfo_t lInfo; +.br + +.br +status = lgGpioGetLineInfo(h, gpio, &lInfo); +.br + +.br +if (status == LG_OKAY) +.br +{ +.br + printf("lFlags=%d name=%s user=%s\n", +.br + lInfo.lines, lInfo.name, lInfo.user)) +.br +} +.br + +.EE + +.IP "\fBint lgGpioGetMode(int handle, int gpio)\fP" +.IP "" 4 +Returns the GPIO mode. + +.br + +.br + +.EX + handle: >= 0 (as returned by \fBlgGpiochipOpen\fP) +.br + gpio: >= 0, as legal for the gpiochip +.br + +.EE + +.br + +.br +If OK returns the GPIO mode. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +Mode bit Value Meaning +.br +0 1 Kernel: In use by the kernel +.br +1 2 Kernel: Output +.br +2 4 Kernel: Active low +.br +3 8 Kernel: Open drain +.br +4 16 Kernel: Open source +.br +5 32 Kernel: --- +.br +6 64 Kernel: --- +.br +7 128 Kernel: --- +.br +8 256 LG: Input +.br +9 512 LG: Output +.br +10 1024 LG: Alert +.br +11 2048 LG: Group +.br +12 4096 LG: --- +.br +13 8192 LG: --- +.br +14 16384 LG: --- +.br +15 32768 LG: --- +.br + +.IP "\fBint lgGpioSetUser(int handle, const char *gpiouser)\fP" +.IP "" 4 +This sets the user string to be associated with each claimed GPIO. + +.br + +.br + +.EX + handle: >= 0 (as returned by \fBlgGpiochipOpen\fP) +.br +gpiouser: a string up to 32 characters long +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +\fBExample\fP +.br + +.EX +status = lgGpioSetUser(h, "my_title"); +.br + +.EE + +.IP "\fBint lgGpioClaimInput(int handle, int lFlags, int gpio)\fP" +.IP "" 4 +This claims a GPIO for input. + +.br + +.br + +.EX +handle: >= 0 (as returned by \fBlgGpiochipOpen\fP) +.br +lFlags: line flags for the GPIO +.br + gpio: the GPIO to be claimed +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +The line flags may be used to set the GPIO +as active low, open drain, or open source. + +.br + +.br +\fBExample\fP +.br + +.EX +// open GPIO 23 for input +.br +status = lgGpioClaimInput(h, 0, 23); +.br + +.EE + +.IP "\fBint lgGpioClaimOutput(int handle, int lFlags, int gpio, int level)\fP" +.IP "" 4 +This claims a GPIO for output. + +.EX +handle: >= 0 (as returned by \fBlgGpiochipOpen\fP) +.br +lFlags: line flags for the GPIO +.br + gpio: the GPIO to be claimed +.br + level: the initial level to set for the GPIO +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +The line flags may be used to set the GPIO +as active low, open drain, or open source. + +.br + +.br +If level is zero the GPIO will be initialised low. If any other +value is used the GPIO will be initialised high. + +.br + +.br +\fBExample\fP +.br + +.EX +// open GPIO 31 for high output +.br +status = lgGpioClaimOutput(h, 0, 31, 1); +.br + +.EE + +.IP "\fBint lgGpioClaimAlert(int handle, int lFlags, int eFlags, int gpio, int nfyHandle)\fP" +.IP "" 4 +This claims a GPIO for alerts on level changes. + +.br + +.br + +.EX + handle: >= 0 (as returned by \fBlgGpiochipOpen\fP) +.br + lFlags: line flags for the GPIO +.br + eFlags: event flags for the GPIO +.br + gpio: >= 0, as legal for the gpiochip +.br +nfyHandle: >= 0 (as returned by \fBlgNotifyOpen\fP) +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +The line flags may be used to set the GPIO +as active low, open drain, or open source. + +.br + +.br +The event flags are used to specify alerts for a rising edge, +falling edge, or both edges. + +.br + +.br +The alerts will be sent to a previously opened notification. If +you don't want them sent to a notification set nfyHandle to -1. + +.br + +.br +The alerts will also be sent to any callback registered for the +GPIO by \fBlgGpioSetAlertsFunc\fP. + +.br + +.br +All GPIO alerts are also sent to a callback registered by +\fBlgGpioSetSamplesFunc\fP. + +.br + +.br +\fBExample\fP +.br + +.EX +status = lgGpioClaimAlert(h, 0, LG_BOTH_EDGES, 16, -1); +.br + +.EE + +.IP "\fBint lgGpioFree(int handle, int gpio)\fP" +.IP "" 4 +This frees a GPIO. + +.br + +.br + +.EX +handle: >= 0 (as returned by \fBlgGpiochipOpen\fP) +.br + gpio: the GPIO to be freed +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +The GPIO may now be claimed by another user or for a different purpose. + +.br + +.br +\fBExample\fP +.br + +.EX +status = lgGpioFree(h, 16); +.br + +.EE + +.IP "\fBint lgGroupClaimInput(int handle, int lFlags, int count, const int *gpios)\fP" +.IP "" 4 +This claims a group of GPIO for inputs. + +.br + +.br + +.EX +handle: >= 0 (as returned by \fBlgGpiochipOpen\fP) +.br +lFlags: line flags for the GPIO group +.br + count: the number of GPIO to claim +.br + gpios: the group GPIO +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +The line flags may be used to set the group +as active low, open drain, or open source. + +.br + +.br +gpios is an array of one or more GPIO. The first GPIO is +called the group leader and is used to reference the group as a whole. + +.br + +.br +\fBExample\fP +.br + +.EX +int buttons[4] = {9, 7, 2, 6}; +.br + +.br +status = lgGroupClaimInput(h, 0, 4, buttons); +.br + +.br +if (status == LG_OKAY) +.br +{ +.br + // OK +.br +} +.br +else +.br +{ +.br + // Error +.br +} +.br + +.EE + +.IP "\fBint lgGroupClaimOutput(int handle, int lFlags, int count, const int *gpios, const int *levels)\fP" +.IP "" 4 +This claims a group of GPIO for outputs. + +.br + +.br + +.EX +handle: >= 0 (as returned by \fBlgGpiochipOpen\fP) +.br +lFlags: line flags for the GPIO group +.br + count: the number of GPIO to claim +.br + gpios: the group GPIO +.br +levels: the initial level for each GPIO +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +The line flags may be used to set the group +as active low, open drain, or open source. + +.br + +.br +gpios is an array of one or more GPIO. The first GPIO is +called the group leader and is used to reference the group as a whole. + +.br + +.br +levels is an array of initialisation values for the GPIO. If a value is +zero the corresponding GPIO will be initialised low. If any other +value is used the corresponding GPIO will be initialised high. + +.br + +.br +\fBExample\fP +.br + +.EX +int leds[7] = {15, 16, 17, 8, 12, 13, 14}; +.br +int levels[7] = { 1, 0, 1, 1, 1, 0, 0}; +.br + +.br +status = lgGroupClaimInput(h, 0, 7, leds, levels); +.br + +.br +if (status == LG_OKAY) +.br +{ +.br + // OK +.br +} +.br +else +.br +{ +.br + // Error +.br +} +.br + +.EE + +.IP "\fBint lgGroupFree(int handle, int gpio)\fP" +.IP "" 4 +This frees all the GPIO associated with a group. + +.br + +.br + +.EX +handle: >= 0 (as returned by \fBlgGpiochipOpen\fP) +.br + gpio: the group to be freed +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +The GPIO may now be claimed by another user or for a different purpose. + +.br + +.br +\fBExample\fP +.br + +.EX +status = lgGroupFree(9); // free buttons +.br + +.EE + +.IP "\fBint lgGpioRead(int handle, int gpio)\fP" +.IP "" 4 +This returns the level of a GPIO. + +.br + +.br + +.EX +handle: >= 0 (as returned by \fBlgGpiochipOpen\fP) +.br + gpio: the GPIO to be read +.br + +.EE + +.br + +.br +If OK returns 0 (low) or 1 (high). + +.br + +.br +On failure returns a negative error code. + +.br + +.br +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. + +.br + +.br +\fBExample\fP +.br + +.EX +level = lgGpioRead(h, 15); // get level of GPIO 15 +.br + +.EE + +.IP "\fBint lgGpioWrite(int handle, int gpio, int level)\fP" +.IP "" 4 +This sets the level of an output GPIO. + +.br + +.br + +.EX +handle: >= 0 (as returned by \fBlgGpiochipOpen\fP) +.br + gpio: the GPIO to be written +.br + level: the level to set +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +This command will work for any GPIO claimed as an output +(even if a member of a group). + +.br + +.br +If level is zero the GPIO will be set low (0). +If any other value is used the GPIO will be set high (1). + +.br + +.br +\fBExample\fP +.br + +.EX +status = lgGpioWrite(h, 23, 1); // set GPIO 23 high +.br + +.EE + +.IP "\fBint lgGroupRead(int handle, int gpio, uint64_t *groupBits)\fP" +.IP "" 4 +This returns the levels read from a group. + +.br + +.br + +.EX + handle: >= 0 (as returned by \fBlgGpiochipOpen\fP) +.br + gpio: the group to be read +.br +groupBits: a pointer to a 64-bit memory area for the returned levels +.br + +.EE + +.br + +.br +If OK returns the group size and updates groupBits. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +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. + +.br + +.br +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. + +.br + +.br +After a successful read groupBits is set as follows. + +.br + +.br +Bit 0 is the level of the group leader. +.br +Bit 1 is the level of the second GPIO in the group. +.br +Bit x is the level of GPIO x+1 of the group. + +.br + +.br +\fBExample\fP +.br + +.EX +// assuming a read group of 4 buttons: 9, 7, 2, 6. +.br +uint64_t bits; +.br + +.br +size = lgGroupRead(h, 9, &bits); // 9 is buttons group leader +.br + +.br +if (size >= 0) // size of group is returned so size will be 4 +.br +{ +.br + level_9 = (bits >> 0) & 1; +.br + level_7 = (bits >> 1) & 1; +.br + level_2 = (bits >> 2) & 1; +.br + level_6 = (bits >> 3) & 1; +.br +} +.br +else +.br +{ +.br + // error +.br +} +.br + +.EE + +.br + +.br + +.IP "\fBint lgGroupWrite(int handle, int gpio, uint64_t groupBits, uint64_t groupMask)\fP" +.IP "" 4 +This sets the levels of an output group. + +.br + +.br + +.EX + handle: >= 0 (as returned by \fBlgGpiochipOpen\fP) +.br + gpio: the group to be written +.br +groupBits: the level to set if the corresponding bit in groupMask is set +.br +groupMask: a mask indicating the group GPIO to be updated +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +The values of each GPIO of the group are set according to the bits +.br +of groupBits. + +.br + +.br +Bit 0 sets the level of the group leader. +.br +Bit 1 sets the level of the second GPIO in the group. +.br +Bit x sets the level of GPIO x+1 in the group. + +.br + +.br +However this may be modified by the groupMask. A GPIO is only +updated if the corresponding bit in the mask is 1. + +.br + +.br +\fBExample\fP +.br + +.EX +// assuming an output group of 7 LEDs: 15, 16, 17, 8, 12, 13, 14. +.br + +.br +// switch on all LEDs +.br +status = lgGroupWrite(h, 15, 0x7f, 0x7f); +.br + +.br +// switch off all LEDs +.br +status = lgGroupWrite(h, 15, 0x00, 0x7f); +.br + +.br +// switch on first 4 LEDs, leave others unaltered +.br +status = lgGroupWrite(h, 15, 0x0f, 0x0f); +.br + +.br +// switch on LED attached to GPIO 13, leave others unaltered +.br +status = lgGroupWrite(h, 15, 32, 32); +.br + +.EE + +.IP "\fBint lgTxPulse(int handle, int gpio, int pulseOn, int pulseOff, int pulseOffset, int pulseCycles)\fP" +.IP "" 4 +This starts software timed pulses on an output GPIO. + +.br + +.br + +.EX + handle: >= 0 (as returned by \fBlgGpiochipOpen\fP) +.br + gpio: the GPIO to be written +.br + pulseOn: pulse high time in microseconds +.br + pulseOff: pulse low time in microseconds +.br +pulseOffset: offset from nominal pulse start position +.br +pulseCycles: the number of pulses to be sent, 0 for infinite +.br + +.EE + +.br + +.br +If OK returns the number of entries left in the PWM queue for the GPIO. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +If both pulseOn and pulseOff 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. + +.br + +.br +Each successful call to this function consumes one PWM queue entry. + +.br + +.br +pulseCycles cycles are transmitted (0 means infinite). Each cycle +consists of pulseOn microseconds of GPIO high followed by pulseOff +microseconds of GPIO low. + +.br + +.br +PWM is characterised by two values, its frequency (number of cycles +per second) and its duty cycle (percentage of high time per cycle). + +.br + +.br +The set frequency will be 1000000 / (pulseOn + pulseOff) Hz. + +.br + +.br +The set duty cycle will be pulseOn / (pulseOn + pulseOff) * 100 %. + +.br + +.br +E.g. if pulseOn is 50 and pulseOff is 100 the frequency will be 6666.67 Hz +and the duty cycle will be 33.33 %. + +.br + +.br +pulseOffset is a microsecond offset from the natural start of the PWM cycle. + +.br + +.br +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. + +.br + +.br +Another pulse command may be issued to the GPIO before the last has finished. + +.br + +.br +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. + +.br + +.br +Multiple pulse settings may be queued in this way. + +.br + +.br +\fBExample\fP +.br + +.EX +slots_left = lgTxPulse(h, 8, 100000, 100000, 0, 0); // flash LED at 5 Hz +.br + +.br +slots_left = lgTxPulse(h, 30, 1500, 18500, 0, 0); // move servo to centre +.br + +.br +slots_left = lgTxPulse(h, 30, 2000, 18000, 0, 0); // move servo clockwise +.br + +.EE + +.IP "\fBint lgTxPwm(int handle, int gpio, float pwmFrequency, float pwmDutyCycle, int pwmOffset, int pwmCycles)\fP" +.IP "" 4 +This starts software timed PWM on an output GPIO. + +.br + +.br + +.EX + handle: >= 0 (as returned by \fBlgGpiochipOpen\fP) +.br + gpio: the GPIO to be pulsed +.br +pwmFrequency: PWM frequency in Hz (0=off, 0.1-10000) +.br +pwmDutyCycle: PWM duty cycle in % (0-100) +.br + pwmOffset: offset from nominal pulse start position +.br + pwmCycles: the number of pulses to be sent, 0 for infinite +.br + +.EE + +.br + +.br +If OK returns the number of entries left in the PWM queue for the GPIO. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +Each successful call to this function consumes one PWM queue entry. + +.br + +.br +PWM is characterised by two values, its frequency (number of cycles +per second) and its duty cycle (percentage of high time per cycle). + +.br + +.br +Another PWM command may be issued to the GPIO before the last has finished. + +.br + +.br +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. + +.br + +.br +Multiple PWM settings may be queued in this way. + +.IP "\fBint lgTxServo(int handle, int gpio, int pulseWidth, int servoFrequency, int servoOffset, int servoCycles)\fP" +.IP "" 4 +This starts software timed servo pulses on an output GPIO. + +.br + +.br +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. + +.br + +.br + +.EX + handle: >= 0 (as returned by \fBlgGpiochipOpen\fP) +.br + gpio: the GPIO to be pulsed +.br + pulseWidth: pulse high time in microseconds (0=off, 500-2500) +.br +servoFrequency: the number of pulses per second (40-500). +.br + servoOffset: offset from nominal pulse start position +.br + servoCycles: the number of pulses to be sent, 0 for infinite +.br + +.EE + +.br + +.br +If OK returns the number of entries left in the PWM queue for the GPIO. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +Each successful call to this function consumes one PWM queue entry. + +.br + +.br +Another servo command may be issued to the GPIO before the last +has finished. + +.br + +.br +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. + +.br + +.br +Multiple servo settings may be queued in this way. + +.IP "\fBint lgTxWave(int handle, int gpio, int count, lgPulse_p pulses)\fP" +.IP "" 4 +This starts a wave on an output group of GPIO. + +.br + +.br + +.EX +handle: >= 0 (as returned by \fBlgGpiochipOpen\fP) +.br + gpio: the group leader +.br + count: the number of pulses in the wave +.br +pulses: the pulses +.br + +.EE + +.br + +.br +If OK returns the number of entries left in the wave queue for the group. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +Each successful call to this function consumes one queue entry. + +.br + +.br +This command starts a wave of pulses. + +.br + +.br +pulses is an array of pulses to be transmitted on the group. + +.br + +.br +Each pulse is defined by the following triplet: + +.br + +.br +bits: the levels to set for the selected GPIO +.br +mask: the GPIO to select +.br +delay: the delay in microseconds before the next pulse + +.br + +.br +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. + +.br + +.br +Multiple waves may be queued in this way. + +.br + +.br +\fBExample\fP +.br + +.EX +#include +.br + +.br +#include +.br + +.br +#define PULSES 2000 +.br + +.br +int main(int argc, char *argv[]) +.br +{ +.br + int GPIO[] = {16, 17, 18, 19, 20, 21}; +.br + int levels[] = { 1, 1, 1, 1, 1, 1}; +.br + int h; +.br + int e; +.br + int mask; +.br + int delay; +.br + int p; +.br + lgPulse_t pulses[PULSES]; +.br + +.br + h = lgGpiochipOpen(0); // open /dev/gpiochip0 +.br + +.br + if (h < 0) { printf("ERROR: %s (%d)\n", lgErrStr(h), h); return 1; } +.br + +.br + e = lgGroupClaimOutput(h, 0, 6, GPIO, levels); +.br + +.br + if (e < 0) { printf("ERROR: %s (%d)\n", lgErrStr(e), e); return 1; } +.br + +.br + mask = 0; +.br + p = 0; +.br + +.br + for (p=0; p>2; // see what sort of pattern we get +.br + pulses[p].mask = mask; // with bits and mask changing +.br + pulses[p].delay = (PULSES + 500) - p; +.br + +.br + if (++mask > 0x3f) mask = 0; +.br + } +.br + +.br + lgTxWave(h, GPIO[0], p, pulses); +.br + +.br + while (lgTxBusy(h, GPIO[0], LG_TX_WAVE)) lguSleep(0.1); +.br + +.br + lgGpiochipClose(h); +.br +} +.br + +.EE + +.IP "\fBint lgTxBusy(int handle, int gpio, int kind)\fP" +.IP "" 4 +This returns true if transmissions of the specified kind +are active on the GPIO or group. + +.br + +.br + +.EX +handle: >= 0 (as returned by \fBlgGpiochipOpen\fP) +.br + gpio: the gpio or group to be checked +.br + kind: LG_TX_PWM or LG_TX_WAVE +.br + +.EE + +.br + +.br +If OK returns 1 for busy and 0 for not busy. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +\fBExample\fP +.br + +.EX +while (lgTxBusy(h, 15, LG_TX_PWM)) // wait for PWM to finish on GPIO 15 +.br + lguSleep(0.1); +.br + +.EE + +.IP "\fBint lgTxRoom(int handle, int gpio, int kind)\fP" +.IP "" 4 +This returns the number of entries available for queueing +transmissions of the specified kind on the GPIO or group. + +.br + +.br + +.EX +handle: >= 0 (as returned by \fBlgGpiochipOpen\fP) +.br + gpio: the gpio or group to be checked +.br + kind: LG_TX_PWM or LG_TX_WAVE +.br + +.EE + +.br + +.br +If OK returns the number of free entries (0 if none). + +.br + +.br +On failure returns a negative error code. + +.br + +.br +\fBExample\fP +.br + +.EX +while (lgTxRoom(h, 17, LG_TX_WAVE) > 0)) +.br +{ +.br + // queue another wave +.br +} +.br + +.EE + +.IP "\fBint lgGpioSetDebounce(int handle, int gpio, int debounce_us)\fP" +.IP "" 4 +This sets the debounce time for a GPIO. + +.br + +.br + +.EX + handle: >= 0 (as returned by \fBlgGpiochipOpen\fP) +.br + gpio: the GPIO to be configured +.br +debounce_us: the debounce time in microseconds +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +This only affects alerts. + +.br + +.br +An alert will only be issued if the edge has been stable for at least +debounce microseconds. + +.br + +.br +Generally this is used to debounce mechanical switches (e.g. contact +bounce). + +.br + +.br +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. + +.br + +.br +Note that level changes will be timestamped debounce microseconds +after the actual level change. + +.br + +.br +\fBExample\fP +.br + +.EX +lgSetDebounceTime(h, 16, 1000); // set a millisecond of debounce +.br + +.EE + +.IP "\fBint lgGpioSetWatchdog(int handle, int gpio, int watchdog_us)\fP" +.IP "" 4 +This sets the watchdog time for a GPIO. + +.br + +.br + +.EX + handle: >= 0 (as returned by \fBlgGpiochipOpen\fP) +.br + gpio: the GPIO to be configured +.br +watchdog_us: the watchdog time in microseconds +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +This only affects alerts. + +.br + +.br +A watchdog alert will be sent if no edge alert has been issued +for that GPIO in the previous watchdog microseconds. + +.br + +.br +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. + +.br + +.br +The level is set to LG_TIMEOUT (2) for a watchdog alert. + +.br + +.br +\fBExample\fP +.br + +.EX +lgSetWatchdogTime(h, 17, 200000); // alert if nothing for 0.2 seconds +.br + +.EE + +.IP "\fBint lgGpioSetAlertsFunc(int handle, int gpio, lgGpioAlertsFunc_t cbf, void *userdata)\fP" +.IP "" 4 +This sets up a callback to be called when an alert +GPIO changes state. + +.br + +.br + +.EX + handle: >= 0 (as returned by \fBlgGpiochipOpen\fP) +.br + gpio: the GPIO to be monitored +.br + cbf: the callback function +.br +userdata: a pointer to arbitrary user data +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +\fBExample\fP +.br + +.EX +#include +.br +#include +.br + +.br +#include +.br + +.br +void afunc(int e, lgGpioAlert_p evt, void *data) +.br +{ +.br + int i; +.br + int userdata = *(int*)data; +.br + +.br + for (i=0; i +.br +#include +.br + +.br +#include +.br + +.br +void afunc(int e, lgGpioAlert_p evt, void *data) +.br +{ +.br + int i; +.br + int userdata = *(int*)data; +.br + +.br + for (i=0; i= 0). + +.br + +.br +On failure returns a negative error code. + +.br + +.br +A notification is a method for being notified of GPIO state changes +via a pipe or socket. + +.br + +.br +The notification pipes are created in the library working directory +(see \fBlguGetWorkDir\fP). + +.br + +.br +Pipe notifications for handle x will be available at the pipe +named lgd-nfy* (where * is the handle number). E.g. if the +function returns 15 then the notifications must be read +from lgd-nfy15. + +.br + +.br +Socket notifications are returned to the socket which requested the +handle. + +.br + +.br +\fBExample\fP +.br + +.EX +h = lgNotifyOpen(); +.br + +.br +if (h >= 0) +.br +{ +.br + sprintf(str, "lgd-nfy%d", h); +.br + +.br + fd = open(str, O_RDONLY); +.br + +.br + if (fd >= 0) +.br + { +.br + // Okay. +.br + } +.br + else +.br + { +.br + // Error. +.br + } +.br +} +.br +else +.br +{ +.br + // Error. +.br +} +.br + +.EE + +.IP "\fBint lgNotifyResume(int handle)\fP" +.IP "" 4 +This function restarts notifications on a paused notification. + +.br + +.br + +.EX +handle: >= 0 (as returned by \fBlgNotifyOpen\fP) +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +The notification gets state changes for each associated GPIO. + +.br + +.br +Each notification occupies 16 bytes in the fifo and has the +following structure. + +.br + +.br + +.EX +typedef struct +.br +{ +.br + uint64_t timestamp; // alert time in nanoseconds +.br + uint8_t chip; // gpiochip device number +.br + uint8_t gpio; // offset into gpio device +.br + uint8_t level; // 0=low, 1=high, 2=timeout +.br + uint8_t flags; // none currently defined +.br +} lgGpioReport_t; +.br + +.EE + +.br + +.br +timestamp: the number of nanoseconds since the epoch (start of 1970) +level: indicates the level of the GPIO +.br +flags: no flags are currently defined + +.br + +.br +For future proofing it is probably best to ignore any notification +with non-zero flags. + +.br + +.br +\fBExample\fP +.br + +.EX +// Start notifications for associated GPIO. +.br +lgNotifyResume(h); +.br + +.EE + +.IP "\fBint lgNotifyPause(int handle)\fP" +.IP "" 4 +This function pauses notifications. + +.br + +.br + +.EX +handle: >= 0 (as returned by \fBlgNotifyOpen\fP) +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +Notifications are suspended until \fBlgNotifyResume\fP is called. + +.br + +.br +\fBExample\fP +.br + +.EX +lgNotifyPause(h); +.br + +.EE + +.IP "\fBint lgNotifyClose(int handle)\fP" +.IP "" 4 +This function stops notifications and frees the handle for reuse. + +.br + +.br + +.EX +handle: >= 0 (as returned by \fBlgNotifyOpen\fP) +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +\fBExample\fP +.br + +.EX +lgNotifyClose(h); +.br + +.EE + +.IP "\fBint lgI2cOpen(int i2cDev, int i2cAddr, int i2cFlags)\fP" +.IP "" 4 +This returns a handle for the device at the address on the I2C bus. + +.br + +.br + +.EX + i2cDev: >= 0 +.br + i2cAddr: 0-0x7F +.br +i2cFlags: 0 +.br + +.EE + +.br + +.br +If OK returns a handle (>= 0). + +.br + +.br +On failure returns a negative error code. + +.br + +.br +No flags are currently defined. This parameter should be set to zero. + +.br + +.br +For the SMBus commands the low level transactions are shown at the end +of the function description. The following abbreviations are used. + +.br + +.br + +.EX +S (1 bit) : Start bit +.br +P (1 bit) : Stop bit +.br +Rd/Wr (1 bit) : Read/Write bit. Rd equals 1, Wr equals 0 +.br +A, NA (1 bit) : Accept and not accept bit +.br +Addr (7 bits): I2C 7 bit address +.br +i2cReg (8 bits): Command byte, a byte which often selects a register +.br +Data (8 bits): A data byte +.br +Count (8 bits): A byte defining the length of a block operation +.br + +.br +[..]: Data sent by the device +.br + +.EE + +.IP "\fBint lgI2cClose(int handle)\fP" +.IP "" 4 +This closes the I2C device. + +.br + +.br + +.EX +handle: >= 0 (as returned by \fBlgI2cOpen\fP) +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.IP "\fBint lgI2cWriteQuick(int handle, int bitVal)\fP" +.IP "" 4 +This sends a single bit (in the Rd/Wr bit) to the device. + +.br + +.br + +.EX +handle: >= 0 (as returned by \fBlgI2cOpen\fP) +.br +bitVal: 0-1, the value to write +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +Quick command. SMBus 2.0 5.5.1 + +.EX +S Addr bit [A] P +.br + +.EE + +.IP "\fBint lgI2cWriteByte(int handle, int byteVal)\fP" +.IP "" 4 +This sends a single byte to the device. + +.br + +.br + +.EX + handle: >= 0 (as returned by \fBlgI2cOpen\fP) +.br +byteVal: 0-0xFF, the value to write +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +Send byte. SMBus 2.0 5.5.2 + +.EX +S Addr Wr [A] bVal [A] P +.br + +.EE + +.IP "\fBint lgI2cReadByte(int handle)\fP" +.IP "" 4 +This reads a single byte from the device. + +.br + +.br + +.EX +handle: >= 0 (as returned by \fBlgI2cOpen\fP) +.br + +.EE + +.br + +.br +If OK returns the byte read (0-255). + +.br + +.br +On failure returns a negative error code. + +.br + +.br +Receive byte. SMBus 2.0 5.5.3 + +.EX +S Addr Rd [A] [Data] NA P +.br + +.EE + +.IP "\fBint lgI2cWriteByteData(int handle, int i2cReg, int byteVal)\fP" +.IP "" 4 +This writes a single byte to the specified register of the device. + +.br + +.br + +.EX + handle: >= 0 (as returned by \fBlgI2cOpen\fP) +.br + i2cReg: 0-255, the register to write +.br +byteVal: 0-0xFF, the value to write +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +Write byte. SMBus 2.0 5.5.4 + +.EX +S Addr Wr [A] i2cReg [A] bVal [A] P +.br + +.EE + +.IP "\fBint lgI2cWriteWordData(int handle, int i2cReg, int wordVal)\fP" +.IP "" 4 +This writes a single 16 bit word to the specified register of the device. + +.br + +.br + +.EX + handle: >= 0 (as returned by \fBlgI2cOpen\fP) +.br + i2cReg: 0-255, the register to write +.br +wordVal: 0-0xFFFF, the value to write +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +Write word. SMBus 2.0 5.5.4 + +.EX +S Addr Wr [A] i2cReg [A] wValLow [A] wValHigh [A] P +.br + +.EE + +.IP "\fBint lgI2cReadByteData(int handle, int i2cReg)\fP" +.IP "" 4 +This reads a single byte from the specified register of the device. + +.br + +.br + +.EX +handle: >= 0 (as returned by \fBlgI2cOpen\fP) +.br +i2cReg: 0-255, the register to read +.br + +.EE + +.br + +.br +If OK returns the byte read (0-255). + +.br + +.br +On failure returns a negative error code. + +.br + +.br +Read byte. SMBus 2.0 5.5.5 + +.EX +S Addr Wr [A] i2cReg [A] S Addr Rd [A] [Data] NA P +.br + +.EE + +.IP "\fBint lgI2cReadWordData(int handle, int i2cReg)\fP" +.IP "" 4 +This reads a single 16 bit word from the specified register of the device. + +.br + +.br + +.EX +handle: >= 0 (as returned by \fBlgI2cOpen\fP) +.br +i2cReg: 0-255, the register to read +.br + +.EE + +.br + +.br +If OK returns the word read (0-65535). + +.br + +.br +On failure returns a negative error code. + +.br + +.br +Read word. SMBus 2.0 5.5.5 + +.EX +S Addr Wr [A] i2cReg [A] S Addr Rd [A] [DataLow] A [DataHigh] NA P +.br + +.EE + +.IP "\fBint lgI2cProcessCall(int handle, int i2cReg, int wordVal)\fP" +.IP "" 4 +This writes 16 bits of data to the specified register of the device +and reads 16 bits of data in return. + +.br + +.br + +.EX + handle: >= 0 (as returned by \fBlgI2cOpen\fP) +.br + i2cReg: 0-255, the register to write/read +.br +wordVal: 0-0xFFFF, the value to write +.br + +.EE + +.br + +.br +If OK returns the word read (0-65535). + +.br + +.br +On failure returns a negative error code. + +.br + +.br +Process call. SMBus 2.0 5.5.6 + +.EX +S Addr Wr [A] i2cReg [A] wValLow [A] wValHigh [A] +.br + S Addr Rd [A] [DataLow] A [DataHigh] NA P +.br + +.EE + +.IP "\fBint lgI2cWriteBlockData(int handle, int i2cReg, const char *txBuf, int count)\fP" +.IP "" 4 +This writes up to 32 bytes to the specified register of the device. + +.br + +.br + +.EX +handle: >= 0 (as returned by \fBlgI2cOpen\fP) +.br +i2cReg: 0-255, the register to write +.br + txBuf: an array with the data to send +.br + count: 1-32, the number of bytes to write +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +Block write. SMBus 2.0 5.5.7 + +.EX +S Addr Wr [A] i2cReg [A] count [A] +.br + txBuf0 [A] txBuf1 [A] ... [A] txBufn [A] P +.br + +.EE + +.IP "\fBint lgI2cReadBlockData(int handle, int i2cReg, char *rxBuf)\fP" +.IP "" 4 +This reads a block of up to 32 bytes from the specified register of +the device. + +.br + +.br + +.EX +handle: >= 0 (as returned by \fBlgI2cOpen\fP) +.br +i2cReg: 0-255, the register to read +.br + rxBuf: an array to receive the read data +.br + +.EE + +.br + +.br +The amount of returned data is set by the device. + +.br + +.br +If OK returns the count of bytes read (0-32) and updates rxBuf. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +Block read. SMBus 2.0 5.5.7 + +.EX +S Addr Wr [A] i2cReg [A] +.br + S Addr Rd [A] [Count] A [rxBuf0] A [rxBuf1] A ... A [rxBufn] NA P +.br + +.EE + +.IP "\fBint lgI2cBlockProcessCall(int handle, int i2cReg, char *ioBuf, int count)\fP" +.IP "" 4 +This writes data bytes to the specified register of the device +and reads a device specified number of bytes of data in return. + +.br + +.br + +.EX +handle: >= 0 (as returned by \fBlgI2cOpen\fP) +.br +i2cReg: 0-255, the register to write/read +.br + ioBuf: an array with the data to send and to receive the read data +.br + count: 1-32, the number of bytes to write +.br + +.EE + +.br + +.br +If OK returns the count of bytes read (0-32) and updates ioBuf. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +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. + +.br + +.br +Block write-block read. SMBus 2.0 5.5.8 + +.EX +S Addr Wr [A] i2cReg [A] count [A] ioBuf0 [A] ... ioBufn [A] +.br + S Addr Rd [A] [Count] A [ioBuf0] A ... [ioBufn] A P +.br + +.EE + +.IP "\fBint lgI2cReadI2CBlockData(int handle, int i2cReg, char *rxBuf, int count)\fP" +.IP "" 4 +This reads count bytes from the specified register of the device. +The count may be 1-32. + +.br + +.br + +.EX +handle: >= 0 (as returned by \fBlgI2cOpen\fP) +.br +i2cReg: 0-255, the register to read +.br + rxBuf: an array to receive the read data +.br + count: 1-32, the number of bytes to read +.br + +.EE + +.br + +.br +If OK returns the count of bytes read (0-32) and updates rxBuf. + +.br + +.br +On failure returns a negative error code. + +.br + +.br + +.EX +S Addr Wr [A] i2cReg [A] +.br + S Addr Rd [A] [rxBuf0] A [rxBuf1] A ... A [rxBufn] NA P +.br + +.EE + +.IP "\fBint lgI2cWriteI2CBlockData(int handle, int i2cReg, const char *txBuf, int count)\fP" +.IP "" 4 +This writes 1 to 32 bytes to the specified register of the device. + +.br + +.br + +.EX +handle: >= 0 (as returned by \fBlgI2cOpen\fP) +.br +i2cReg: 0-255, the register to write +.br + txBuf: the data to write +.br + count: 1-32, the number of bytes to write +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br + +.EX +S Addr Wr [A] i2cReg [A] txBuf0 [A] txBuf1 [A] ... [A] txBufn [A] P +.br + +.EE + +.IP "\fBint lgI2cReadDevice(int handle, char *rxBuf, int count)\fP" +.IP "" 4 +This reads count bytes from the raw device into rxBuf. + +.br + +.br + +.EX +handle: >= 0 (as returned by \fBlgI2cOpen\fP) +.br + rxBuf: an array to receive the read data bytes +.br + count: >0, the number of bytes to read +.br + +.EE + +.br + +.br +If OK returns count (>0) and updates rxBuf. + +.br + +.br +On failure returns a negative error code. + +.br + +.br + +.EX +S Addr Rd [A] [rxBuf0] A [rxBuf1] A ... A [rxBufn] NA P +.br + +.EE + +.IP "\fBint lgI2cWriteDevice(int handle, const char *txBuf, int count)\fP" +.IP "" 4 +This writes count bytes from txBuf to the raw device. + +.br + +.br + +.EX +handle: >= 0 (as returned by \fBlgI2cOpen\fP) +.br + txBuf: an array containing the data bytes to write +.br + count: >0, the number of bytes to write +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br + +.EX +S Addr Wr [A] txBuf0 [A] txBuf1 [A] ... [A] txBufn [A] P +.br + +.EE + +.IP "\fBint lgI2cSegments(int handle, lgI2cMsg_t *segs, int count)\fP" +.IP "" 4 +This function executes multiple I2C segments in one transaction by +calling the I2C_RDWR ioctl. + +.br + +.br + +.EX +handle: >= 0 (as returned by \fBlgI2cOpen\fP) +.br + segs: an array of I2C segments +.br + count: >0, the number of I2C segments +.br + +.EE + +.br + +.br +If OK returns the number of segments executed. + +.br + +.br +On failure returns a negative error code. + +.IP "\fBint lgI2cZip(int handle, const char *txBuf, int txCount, char *rxBuf, int rxCount)\fP" +.IP "" 4 +This function executes a sequence of I2C operations. The +operations to be performed are specified by the contents of txBuf +which contains the concatenated command codes and associated data. + +.br + +.br + +.EX + handle: >= 0 (as returned by \fBlgI2cOpen\fP) +.br + txBuf: pointer to the concatenated I2C commands, see below +.br + txCount: size of command buffer +.br + rxBuf: pointer to buffer to hold returned data +.br + rxCount: size of receive buffer +.br + +.EE + +.br + +.br +If OK returns the count of bytes read (which may be 0) and updates rxBuf. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +The following command codes are supported: + +.br + +.br +Name Cmd & Data Meaning +.br +End 0 No more commands +.br +Escape 1 Next P is two bytes +.br +Address 2 P Set I2C address to P +.br +Flags 3 lsb msb Set I2C flags to lsb + (msb << 8) +.br +Read 4 P Read P bytes of data +.br +Write 5 P ... Write P bytes of data +.br + +.br + +.br +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). + +.br + +.br +The address defaults to that associated with the handle. +The flags default to 0. The address and flags maintain their +previous value until updated. + +.br + +.br +The returned I2C data is stored in consecutive locations of rxBuf. + +.br + +.br +\fBExample\fP +.br + +.EX +Set address 0x53, write 0x32, read 6 bytes +.br +Set address 0x1E, write 0x03, read 6 bytes +.br +Set address 0x68, write 0x1B, read 8 bytes +.br +End +.br + +.br +2 0x53 5 1 0x32 4 6 +.br +2 0x1E 5 1 0x03 4 6 +.br +2 0x68 5 1 0x1B 4 8 +.br +0 +.br + +.EE + +.IP "\fBint lgSerialOpen(const char *serDev, int serBaud, int serFlags)\fP" +.IP "" 4 +This function opens a serial device at a specified baud rate +and with specified flags. The device must be present in /dev. + +.br + +.br + +.EX + serDev: the serial device to open +.br + serBaud: the baud rate in bits per second, see below +.br +serFlags: 0 +.br + +.EE + +.br + +.br +If OK returns a handle (>= 0). + +.br + +.br +On failure returns a negative error code. + +.br + +.br +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. + +.br + +.br +No flags are currently defined. This parameter should be set to zero. + +.IP "\fBint lgSerialClose(int handle)\fP" +.IP "" 4 +This function closes the serial device. + +.br + +.br + +.EX +handle: >= 0 (as returned by \fBlgSerialOpen\fP) +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.IP "\fBint lgSerialWriteByte(int handle, int byteVal)\fP" +.IP "" 4 +This function writes the byte to the serial device. + +.br + +.br + +.EX + handle: >= 0 (as returned by \fBlgSerialOpen\fP) +.br +byteVal: the byte to write. +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.IP "\fBint lgSerialReadByte(int handle)\fP" +.IP "" 4 +This function reads a byte from the serial device. + +.br + +.br + +.EX +handle: >= 0 (as returned by \fBlgSerialOpen\fP) +.br + +.EE + +.br + +.br +If OK returns the byte read (0-255). + +.br + +.br +On failure returns a negative error code. + +.IP "\fBint lgSerialWrite(int handle, const char *txBuf, int count)\fP" +.IP "" 4 +This function writes count bytes from txBuf to the the serial device. + +.br + +.br + +.EX +handle: >= 0 (as returned by \fBlgSerialOpen\fP) +.br + txBuf: the array of bytes to write +.br + count: the number of bytes to write +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.IP "\fBint lgSerialRead(int handle, char *rxBuf, int count)\fP" +.IP "" 4 +This function reads up count bytes from the the serial device +and writes them to rxBuf. + +.br + +.br + +.EX +handle: >= 0 (as returned by \fBlgSerialOpen\fP) +.br + rxBuf: an array to receive the read data +.br + count: the maximum number of bytes to read +.br + +.EE + +.br + +.br +If OK returns the count of bytes read (>= 0) and updates rxBuf. + +.br + +.br +On failure returns a negative error code. + +.br + +.br + +.IP "\fBint lgSerialDataAvailable(int handle)\fP" +.IP "" 4 +This function returns the count of bytes available +to be read from the device. + +.br + +.br + +.EX +handle: >= 0 (as returned by \fBlgSerialOpen\fP) +.br + +.EE + +.br + +.br +If OK returns the count of bytes available(>= 0). + +.br + +.br +On failure returns a negative error code. + +.IP "\fBint lgSpiOpen(int spiDev, int spiChan, int spiBaud, int spiFlags)\fP" +.IP "" 4 +This function returns a handle for the SPI device on the channel. + +.br + +.br + +.EX + spiDev: >= 0 +.br + spiChan: >= 0 +.br + spiBaud: the SPI speed to set in bits per second +.br +spiFlags: see below +.br + +.EE + +.br + +.br +If OK returns a handle (>= 0). + +.br + +.br +On failure returns a negative error code. + +.br + +.br +The flags may be used to modify the default behaviour. + +.br + +.br +spiFlags consists of the least significant 2 bits. + +.br + +.br + +.EX +1 0 +.br +m m +.br + +.EE + +.br + +.br +mm defines the SPI mode. + +.br + +.br + +.EX +Mode POL PHA +.br + 0 0 0 +.br + 1 0 1 +.br + 2 1 0 +.br + 3 1 1 +.br + +.EE + +.br + +.br +The other bits in flags should be set to zero. + +.IP "\fBint lgSpiClose(int handle)\fP" +.IP "" 4 +This functions closes the SPI device. + +.br + +.br + +.EX +handle: >= 0 (as returned by \fBlgSpiOpen\fP) +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.IP "\fBint lgSpiRead(int handle, char *rxBuf, int count)\fP" +.IP "" 4 +This function reads count bytes of data from the SPI +device. + +.br + +.br + +.EX +handle: >= 0 (as returned by \fBlgSpiOpen\fP) +.br + rxBuf: an array to receive the read data bytes +.br + count: the number of bytes to read +.br + +.EE + +.br + +.br +If OK returns the count of bytes read and updates rxBuf. + +.br + +.br +On failure returns a negative error code. + +.IP "\fBint lgSpiWrite(int handle, const char *txBuf, int count)\fP" +.IP "" 4 +This function writes count bytes of data from txBuf to the SPI +device. + +.br + +.br + +.EX +handle: >= 0 (as returned by \fBlgSpiOpen\fP) +.br + txBuf: the data bytes to write +.br + count: the number of bytes to write +.br + +.EE + +.br + +.br +If OK returns the count of bytes written. + +.br + +.br +On failure returns a negative error code. + +.IP "\fBint lgSpiXfer(int handle, const char *txBuf, char *rxBuf, int count)\fP" +.IP "" 4 +This function transfers count bytes of data from txBuf to the SPI +device. Simultaneously count bytes of +data are read from the device and placed in rxBuf. + +.br + +.br + +.EX +handle: >= 0 (as returned by \fBlgSpiOpen\fP) +.br + txBuf: the data bytes to write +.br + rxBuf: the received data bytes +.br + count: the number of bytes to transfer +.br + +.EE + +.br + +.br +If OK returns the count of bytes transferred and updates rxBuf. + +.br + +.br +On failure returns a negative error code. + +.IP "\fBpthread_t *lgThreadStart(lgThreadFunc_t f, void *userdata)\fP" +.IP "" 4 +Starts a new thread of execution with f as the main routine. + +.br + +.br + +.EX + f: the main function for the new thread +.br +userdata: a pointer to arbitrary user data +.br + +.EE + +.br + +.br +If OK returns a pointer to a pthread_t. + +.br + +.br +On failure returns NULL. + +.br + +.br +The function is passed the single argument arg. + +.br + +.br +The thread can be cancelled by passing the pointer to pthread_t to +\fBlgThreadStop\fP. + +.br + +.br +\fBExample\fP +.br + +.EX +#include +.br +#include +.br +#include +.br + +.br +void *myfunc(void *arg) +.br +{ +.br + while (1) +.br + { +.br + printf("%s\n", arg); +.br + sleep(1); +.br + } +.br +} +.br + +.br +int main(int argc, char *argv[]) +.br +{ +.br + pthread_t *p1, *p2, *p3; +.br + +.br + p1 = lgThreadStart(myfunc, "thread 1"); sleep(3); +.br + +.br + p2 = lgThreadStart(myfunc, "thread 2"); sleep(3); +.br + +.br + p3 = lgThreadStart(myfunc, "thread 3"); sleep(3); +.br + +.br + lgThreadStop(p3); sleep(3); +.br + +.br + lgThreadStop(p2); sleep(3); +.br + +.br + lgThreadStop(p1); sleep(3); +.br +} +.br + +.EE + +.IP "\fBvoid lgThreadStop(pthread_t *pth)\fP" +.IP "" 4 +Cancels the thread pointed at by pth. + +.br + +.br + +.EX +pth: a thread pointer (as returned by \fBlgThreadStart\fP) +.br + +.EE + +.br + +.br +No value is returned. + +.br + +.br +The thread to be stopped should have been started with \fBlgThreadStart\fP. + +.IP "\fBuint64_t lguTimestamp(void)\fP" +.IP "" 4 +Returns the current timestamp. + +.br + +.br +The timestamp is the number of nanoseconds since the epoch (start +of 1970). + +.IP "\fBdouble lguTime(void)\fP" +.IP "" 4 +Returns the current time. + +.br + +.br +The time is the number of seconds since the epoch (start +of 1970). + +.IP "\fBvoid lguSleep(double sleepSecs)\fP" +.IP "" 4 +Sleeps for the specified number of seconds. + +.br + +.br + +.EX +sleepSecs: how long to sleep in seconds +.br + +.EE + +.IP "\fBint lguSbcName(char *rxBuf, int count)\fP" +.IP "" 4 +Copies the host name of the machine running the lgpio library +to the supplied buffer. Up to count characters are copied. + +.br + +.br + +.EX +rxBuf: a buffer to receive the host name +.br +count: the size of the rxBuf +.br + +.EE + +.br + +.br +If OK returns the count of bytes copied and updates rxBuf. + +.br + +.br +On failure returns a negative error code. + +.IP "\fBint lguVersion(void)\fP" +.IP "" 4 +Returns the lgpiolibrary version number. + +.IP "\fBint lguGetInternal(int cfgId, uint64_t *cfgVal)\fP" +.IP "" 4 +Get an internal configuration value. + +.br + +.br + +.EX + cfgId: the item. +.br +cfgVal: a variable to receive the returned value +.br + +.EE + +.br + +.br +If OK returns 0 and updates cfgVal. + +.br + +.br +On failure returns a negative error code. + +.IP "\fBint lguSetInternal(int cfgId, uint64_t cfgVal)\fP" +.IP "" 4 +Set an internal configuration value. + +.br + +.br + +.EX + cfgId: the item +.br +cfgVal: the value to set +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.IP "\fBconst char *lgErrStr(int error)\fP" +.IP "" 4 +Returns the error text for an error code. + +.br + +.br + +.EX +error: the error code +.br + +.EE + +.IP "\fBvoid lguSetWorkDir(const char *dirPath)\fP" +.IP "" 4 +Sets the library working directory. + +.br + +.br +This function has no affect if the working directory has already +been set. + +.br + +.br + +.EX +dirPath: the directory to set as the working directory +.br + +.EE + +.br + +.br +If dirPath does not start with a / the directory is relative to +the library launch directory. + +.IP "\fBconst char *lguGetWorkDir(void)\fP" +.IP "" 4 +Returns the library working directory. +.SH PARAMETERS + +.br + +.br + +.IP "\fBbitVal\fP" 0 +A value of 0 or 1. + +.br + +.br + +.IP "\fBbyteVal\fP: 0-255" 0 +An 8-bit byte value. + +.br + +.br + +.IP "\fBcbf\fP" 0 +An alerts callback function. + +.br + +.br + +.IP "\fBcfgId\fP" 0 +A number identifying a configuration item. + +.br + +.br + +.EX +LG_CFG_ID_DEBUG_LEVEL 0 +.br +LG_CFG_ID_MIN_DELAY 1 +.br + +.EE + +.br + +.br + +.IP "\fBcfgVal\fP" 0 +The value of a configuration item. + +.br + +.br + +.IP "\fB*cfgVal\fP" 0 +The value of a configuration item. + +.br + +.br + +.IP "\fBchar\fP" 0 +A single character, an 8 bit quantity able to store 0-255. + +.br + +.br + +.IP "\fBchipInfo\fP" 0 +A pointer to a lgChipInfo_t object. + +.br + +.br + +.IP "\fBcount\fP" 0 +The number of items. + +.br + +.br + +.IP "\fBdebounce_us\fP" 0 +The debounce time in microseconds. + +.br + +.br + +.IP "\fB*dirPath\fP" 0 +A directory path which. + +.br + +.br + +.IP "\fBdouble\fP" 0 +A floating point number. + +.br + +.br + +.IP "\fBeFlags\fP" 0 + +.br + +.br +The type of GPIO edge to generate an alert. See \fBlgGpioClaimAlert\fP. + +.br + +.br + +.EX +LG_RISING_EDGE +.br +LG_FALLING_EDGE +.br +LG_BOTH_EDGES +.br + +.EE + +.br + +.br + +.IP "\fBerror\fP" 0 +An error code. All error codes are negative. + +.br + +.br + +.IP "\fBf\fP" 0 +A function. + +.br + +.br + +.IP "\fBfloat\fP" 0 +A floating point number + +.br + +.br + +.IP "\fBgpio\fP" 0 +A GPIO number, the offset of the GPIO from the base of the gpiochip. +Offsets start at 0. + +.br + +.br + +.IP "\fBgpioDev\fP: >= 0" 0 +The device number of a gpiochip. + +.br + +.br + +.IP "\fB*gpios\fP" 0 +An array of GPIO numbers. + +.br + +.br + +.IP "\fB*gpiouser\fP" 0 +A string of up to 32 characters denoting the user of a GPIO. + +.br + +.br + +.IP "\fBgroupBits\fP" 0 +A 64-bit value used to set the levels of a group. + +.br + +.br +Set bit x to set GPIO x of the group high. + +.br + +.br +Clear bit x to set GPIO x of the group low. + +.br + +.br + +.IP "\fB*groupBits\fP" 0 +A 64-bit value denoting the levels of a group. + +.br + +.br +If bit x is set then GPIO x of the group is high. + +.br + +.br + +.IP "\fBgroupMask\fP" 0 +A 64-bit value used to determine which members of a group +should be updated. + +.br + +.br +Set bit x to update GPIO x of the group. + +.br + +.br +Clear bit x to leave GPIO x of the group unaltered. + +.br + +.br + +.IP "\fBhandle\fP: >= 0" 0 + +.br + +.br +A number referencing an object opened by one of + +.br + +.br +\fBlgGpiochipOpen\fP +.br +\fBlgI2cOpen\fP +.br +\fBlgNotifyOpen\fP +.br +\fBlgSerialOpen\fP +.br +\fBlgSpiOpen\fP + +.br + +.br + +.IP "\fBi2cAddr\fP: 0-0x7F" 0 +The address of a device on the I2C bus. + +.br + +.br + +.IP "\fBi2cDev\fP: >= 0" 0 +An I2C device number. + +.br + +.br + +.IP "\fBi2cFlags\fP: 0" 0 +Flags which modify an I2C open command. None are currently defined. + +.br + +.br + +.IP "\fBi2cReg\fP: 0-255" 0 +A register of an I2C device. + +.br + +.br + +.IP "\fBint\fP" 0 +A whole number, negative or positive. + +.br + +.br + +.IP "\fB*ioBuf\fP" 0 +A pointer to a buffer used to hold data to send and the data received. + +.br + +.br + +.IP "\fBkind\fP: LG_TX_PWM or LG_TX_WAVE" 0 +A type of transmission: PWM or wave. + +.br + +.br + +.IP "\fBlevel\fP" 0 +A GPIO level (0 or 1). + +.br + +.br + +.IP "\fB*levels\fP" 0 +An array of GPIO levels. + +.br + +.br + +.IP "\fBlFlags\fP" 0 + +.br + +.br +line flags for the GPIO. + +.br + +.br +The following values may be or'd to form the value. + +.br + +.br + +.EX +LG_SET_ACTIVE_LOW +.br +LG_SET_OPEN_DRAIN +.br +LG_SET_OPEN_SOURCE +.br + +.EE + +.br + +.br + +.IP "\fBlgChipInfo_p\fP" 0 +A pointer to a lgChipInfo_t object. + +.br + +.br + +.EX +typedef struct lgChipInfo_s +.br +{ +.br + uint32_t lines; // number of GPIO +.br + char name[LG_GPIO_NAME_LEN]; // Linux name +.br + char label[LG_GPIO_LABEL_LEN]; // functional name +.br +} lgChipInfo_t, *lgChipInfo_p; +.br + +.EE + +.br + +.br + +.IP "\fBlgGpioAlert_t\fP" 0 + +.br + +.br + +.EX +typedef struct lgGpioAlert_s +.br +{ +.br + lgGpioReport_t report; +.br + int nfyHandle; +.br +} lgGpioAlert_t, *lgGpioAlert_p; +.br + +.EE + +.br + +.br +See \fBlgGpioReport_t\fP. + +.br + +.br + +.br + +.br + +.IP "\fBlgGpioAlertsFunc_t\fP" 0 + +.br + +.br + +.EX +typedef void (*lgGpioAlertsFunc_t) +.br + (int num_alerts, lgGpioAlert_p alerts, void *userdata); +.br + +.EE + +.br + +.br +See \fBlgGpioAlert_t\fP. + +.br + +.br + +.IP "\fBlgGpioReport_t\fP" 0 + +.br + +.br + +.EX +typedef struct +.br +{ +.br + uint64_t timestamp; // alert time in nanoseconds +.br + uint8_t chip; // gpiochip device number +.br + uint8_t gpio; // offset into gpio device +.br + uint8_t level; // 0=low, 1=high, 2=watchdog +.br + uint8_t flags; // none defined, ignore report if non-zero +.br +} lgGpioReport_t; +.br + +.EE + +.br + +.br + +.IP "\fBlgI2cMsg_t\fP" 0 + +.EX +typedef struct +.br +{ +.br + uint16_t addr; // slave address +.br + uint16_t flags; +.br + uint16_t len; // msg length +.br + uint8_t *buf; // pointer to msg data +.br +} lgI2cMsg_t; +.br + +.EE + +.br + +.br + +.IP "\fBlgLineInfo_p\fP" 0 +A pointer to a lgLineInfo_t object. + +.br + +.br + +.EX +typedef struct lgLine_s +.br +{ +.br + uint32_t offset; // GPIO number +.br + uint32_t lFlags; +.br + char name[LG_GPIO_NAME_LEN]; // GPIO name +.br + char user[LG_GPIO_USER_LEN]; // user +.br +} lgLineInfo_t, *lgLineInfo_p; +.br + +.EE + +.br + +.br + +.IP "\fBlgPulse_p\fP" 0 +A pointer to a lgPulse_t object. + +.br + +.br + +.EX +typedef struct lgPulse_s +.br +{ +.br + uint64_t bits; +.br + uint64_t mask; +.br + int64_t delay; +.br +} lgPulse_t, *lgPulse_p; +.br + +.EE + +.br + +.br + +.IP "\fBlgThreadFunc_t\fP" 0 + +.EX +typedef void *(lgThreadFunc_t) (void *); +.br + +.EE + +.br + +.br + +.IP "\fBlineInfo\fP" 0 +A pointer to a lgLineInfo_t object. + +.br + +.br + +.IP "\fBnfyHandle\fP: >= 0" 0 +This associates a notification with a GPIO alert. + +.br + +.br + +.IP "\fB*pth\fP" 0 +A thread identifier, returned by \fBlgGpioStartThread\fP. + +.br + +.br + +.IP "\fBpthread_t\fP" 0 +A thread identifier. + +.br + +.br + +.IP "\fBpulseCycles\fP: >= 0" 0 +The number of PWM pulses to generate. A value of 0 means infinite. + +.br + +.br + +.IP "\fBpulseOff\fP: >= 0" 0 +The off period for a PWM pulse in microseconds. + +.br + +.br + +.IP "\fBpulseOffset\fP: >= 0" 0 +The offset in microseconds from the nominal PWM pulse start. + +.br + +.br + +.IP "\fBpulseOn\fP: >= 0" 0 +The on period for a PWM pulse in microseconds. + +.br + +.br + +.IP "\fBpulses\fP" 0 +An pointer to an array of lgPulse_t objects. + +.br + +.br + +.IP "\fBpulseWidth\fP: 0, 500-2500 microseconds" 0 +Servo pulse width + +.br + +.br + +.IP "\fBpwmCycles\fP: >= 0" 0 +The number of PWM pulses to generate. A value of 0 means infinite. + +.br + +.br + +.IP "\fBpwmDutyCycle\fP: 0-100 %" 0 +PWM duty cycle % + +.br + +.br + +.IP "\fBpwmFrequency\fP: 0.1-10000 Hz" 0 +PWM frequency + +.br + +.br + +.IP "\fBpwmOffset\fP: >= 0" 0 +The offset in microseconds from the nominal PWM pulse start. + +.br + +.br + +.IP "\fB*rxBuf\fP" 0 +A pointer to a buffer used to receive data. + +.br + +.br + +.IP "\fBrxCount\fP" 0 +The size of an input buffer. + +.br + +.br + +.IP "\fB*segs\fP" 0 +An array of segments which make up a combined I2C transaction. + +.br + +.br + +.IP "\fBserBaud\fP" 0 +The speed of serial communication in bits per second. + +.br + +.br + +.IP "\fB*serDev\fP" 0 +The name of a serial tty device, e.g. /dev/ttyAMA0, /dev/ttyUSB0, /dev/tty1. + +.br + +.br + +.IP "\fBserFlags\fP" 0 +Flags which modify a serial open command. None are currently defined. + +.br + +.br + +.IP "\fBservoCycles\fP: >= 0" 0 +The number of servo pulses to generate. A value of 0 means infinite. + +.br + +.br + +.IP "\fBservoFrequency\fP: 40-500 Hz" 0 +Servo pulse frequency + +.br + +.br + +.IP "\fBservoOffset\fP: >= 0" 0 +The offset in microseconds from the nominal servo pulse start. + +.br + +.br + +.IP "\fBsleepSecs\fP: >= 0.0" 0 +The number of seconds to sleep (may be fractional). + +.br + +.br + +.IP "\fBspiBaud\fP" 0 +The speed of serial communication in bits per second. + +.br + +.br + +.IP "\fBspiChan\fP" 0 +A SPI channel, >= 0. + +.br + +.br + +.IP "\fBspiDev\fP" 0 +A SPI device, >= 0. + +.br + +.br + +.IP "\fBspiFlags\fP" 0 +See \fBlgSpiOpen\fP. + +.br + +.br + +.IP "\fB*txBuf\fP" 0 +An pointer to a buffer of data to transmit. + +.br + +.br + +.IP "\fBtxCount\fP" 0 +The size of an output buffer. + +.br + +.br + +.IP "\fBuint64_t\fP" 0 +A 64-bit unsigned value. + +.br + +.br + +.IP "\fB*userdata\fP" 0 +A pointer to arbitrary user data. This may be used to identify the instance. + +.br + +.br +You must ensure that the pointer is in scope at the time it is processed. If +it is a pointer to a global this is automatic. Do not pass the address of a +local variable. If you want to pass a transient object then use the +following technique. + +.br + +.br +In the calling function: + +.br + +.br + +.EX +user_type *userdata; +.br +.br +.br +user_type my_userdata; +.br + +.br +userdata = malloc(sizeof(user_type)); +.br +.br +.br +*userdata = my_userdata; +.br + +.EE + +.br + +.br +In the receiving function: + +.br + +.br + +.EX +user_type my_userdata = *(user_type*)userdata; +.br + +.br +free(userdata); +.br + +.EE + +.br + +.br + +.IP "\fBvoid\fP" 0 +Denoting no parameter is required. + +.br + +.br + +.IP "\fBwatchdog_us\fP" 0 +The watchdog time in microseconds. + +.br + +.br + +.IP "\fBwordVal\fP: 0-65535" 0 +A 16-bit value. + +.br + +.br +.SH Error Codes + +.EX + +.br +LG_OKAY 0 // No error +.br +LG_INIT_FAILED -1 // initialisation failed +.br +LG_BAD_MICROS -2 // micros not 0-999999 +.br +LG_BAD_PATHNAME -3 // can not open pathname +.br +LG_NO_HANDLE -4 // no handle available +.br +LG_BAD_HANDLE -5 // unknown handle +.br +LG_BAD_SOCKET_PORT -6 // socket port not 1024-32000 +.br +LG_NOT_PERMITTED -7 // GPIO operation not permitted +.br +LG_SOME_PERMITTED -8 // one or more GPIO not permitted +.br +LG_BAD_SCRIPT -9 // invalid script +.br +LG_BAD_TX_TYPE -10 // bad tx type for GPIO and group +.br +LG_GPIO_IN_USE -11 // GPIO already in use +.br +LG_BAD_PARAM_NUM -12 // script parameter id not 0-9 +.br +LG_DUP_TAG -13 // script has duplicate tag +.br +LG_TOO_MANY_TAGS -14 // script has too many tags +.br +LG_BAD_SCRIPT_CMD -15 // illegal script command +.br +LG_BAD_VAR_NUM -16 // script variable id not 0-149 +.br +LG_NO_SCRIPT_ROOM -17 // no more room for scripts +.br +LG_NO_MEMORY -18 // can not allocate temporary memory +.br +LG_SOCK_READ_FAILED -19 // socket read failed +.br +LG_SOCK_WRIT_FAILED -20 // socket write failed +.br +LG_TOO_MANY_PARAM -21 // too many script parameters (> 10) +.br +LG_SCRIPT_NOT_READY -22 // script initialising +.br +LG_BAD_TAG -23 // script has unresolved tag +.br +LG_BAD_MICS_DELAY -24 // bad MICS delay (too large) +.br +LG_BAD_MILS_DELAY -25 // bad MILS delay (too large) +.br +LG_I2C_OPEN_FAILED -26 // can not open I2C device +.br +LG_SERIAL_OPEN_FAILED -27 // can not open serial device +.br +LG_SPI_OPEN_FAILED -28 // can not open SPI device +.br +LG_BAD_I2C_BUS -29 // bad I2C bus +.br +LG_BAD_I2C_ADDR -30 // bad I2C address +.br +LG_BAD_SPI_CHANNEL -31 // bad SPI channel +.br +LG_BAD_I2C_FLAGS -32 // bad I2C open flags +.br +LG_BAD_SPI_FLAGS -33 // bad SPI open flags +.br +LG_BAD_SERIAL_FLAGS -34 // bad serial open flags +.br +LG_BAD_SPI_SPEED -35 // bad SPI speed +.br +LG_BAD_SERIAL_DEVICE -36 // bad serial device name +.br +LG_BAD_SERIAL_SPEED -37 // bad serial baud rate +.br +LG_BAD_FILE_PARAM -38 // bad file parameter +.br +LG_BAD_I2C_PARAM -39 // bad I2C parameter +.br +LG_BAD_SERIAL_PARAM -40 // bad serial parameter +.br +LG_I2C_WRITE_FAILED -41 // i2c write failed +.br +LG_I2C_READ_FAILED -42 // i2c read failed +.br +LG_BAD_SPI_COUNT -43 // bad SPI count +.br +LG_SERIAL_WRITE_FAILED -44 // ser write failed +.br +LG_SERIAL_READ_FAILED -45 // ser read failed +.br +LG_SERIAL_READ_NO_DATA -46 // ser read no data available +.br +LG_UNKNOWN_COMMAND -47 // unknown command +.br +LG_SPI_XFER_FAILED -48 // spi xfer/read/write failed +.br +LG_BAD_POINTER -49 // bad (NULL) pointer +.br +LG_MSG_TOOBIG -50 // socket/pipe message too big +.br +LG_BAD_MALLOC_MODE -51 // bad memory allocation mode +.br +LG_TOO_MANY_SEGS -52 // too many I2C transaction segments +.br +LG_BAD_I2C_SEG -53 // an I2C transaction segment failed +.br +LG_BAD_SMBUS_CMD -54 // SMBus command not supported by driver +.br +LG_BAD_I2C_WLEN -55 // bad I2C write length +.br +LG_BAD_I2C_RLEN -56 // bad I2C read length +.br +LG_BAD_I2C_CMD -57 // bad I2C command +.br +LG_FILE_OPEN_FAILED -58 // file open failed +.br +LG_BAD_FILE_MODE -59 // bad file mode +.br +LG_BAD_FILE_FLAG -60 // bad file flag +.br +LG_BAD_FILE_READ -61 // bad file read +.br +LG_BAD_FILE_WRITE -62 // bad file write +.br +LG_FILE_NOT_ROPEN -63 // file not open for read +.br +LG_FILE_NOT_WOPEN -64 // file not open for write +.br +LG_BAD_FILE_SEEK -65 // bad file seek +.br +LG_NO_FILE_MATCH -66 // no files match pattern +.br +LG_NO_FILE_ACCESS -67 // no permission to access file +.br +LG_FILE_IS_A_DIR -68 // file is a directory +.br +LG_BAD_SHELL_STATUS -69 // bad shell return status +.br +LG_BAD_SCRIPT_NAME -70 // bad script name +.br +LG_CMD_INTERRUPTED -71 // Python socket command interrupted +.br +LG_BAD_EVENT_REQUEST -72 // bad event request +.br +LG_BAD_GPIO_NUMBER -73 // bad GPIO number +.br +LG_BAD_GROUP_SIZE -74 // bad group size +.br +LG_BAD_LINEINFO_IOCTL -75 // bad lineinfo IOCTL +.br +LG_BAD_READ -76 // bad GPIO read +.br +LG_BAD_WRITE -77 // bad GPIO write +.br +LG_CANNOT_OPEN_CHIP -78 // can not open gpiochip +.br +LG_GPIO_BUSY -79 // GPIO busy +.br +LG_GPIO_NOT_ALLOCATED -80 // GPIO not allocated +.br +LG_NOT_A_GPIOCHIP -81 // not a gpiochip +.br +LG_NOT_ENOUGH_MEMORY -82 // not enough memory +.br +LG_POLL_FAILED -83 // GPIO poll failed +.br +LG_TOO_MANY_GPIOS -84 // too many GPIO +.br +LG_UNEGPECTED_ERROR -85 // unexpected error +.br +LG_BAD_PWM_MICROS -86 // bad PWM micros +.br +LG_NOT_GROUP_LEADER -87 // GPIO not the group leader +.br +LG_SPI_IOCTL_FAILED -88 // SPI iOCTL failed +.br +LG_BAD_GPIOCHIP -89 // bad gpiochip +.br +LG_BAD_CHIPINFO_IOCTL -90 // bad chipinfo IOCTL +.br +LG_BAD_CONFIG_FILE -91 // bad configuration file +.br +LG_BAD_CONFIG_VALUE -92 // bad configuration value +.br +LG_NO_PERMISSIONS -93 // no permission to perform action +.br +LG_BAD_USERNAME -94 // bad user name +.br +LG_BAD_SECRET -95 // bad secret for user +.br +LG_TX_QUEUE_FULL -96 // TX queue full +.br +LG_BAD_CONFIG_ID -97 // bad configuration id +.br +LG_BAD_DEBOUNCE_MICS -98 // bad debounce microseconds +.br +LG_BAD_WATCHDOG_MICS -99 // bad watchdog microseconds +.br +LG_BAD_SERVO_FREQ -100 // bad servo frequency +.br +LG_BAD_SERVO_WIDTH -101 // bad servo pulsewidth +.br +LG_BAD_PWM_FREQ -102 // bad PWM frequency +.br +LG_BAD_PWM_DUTY -103 // bad PWM dutycycle +.br +LG_GPIO_NOT_AN_OUTPUT -104 // GPIO not set as an output +.br +LG_INVALID_GROUP_ALERT -105 // can not set a group to alert +.br + +.br + +.EE + +.SH SEE ALSO + +rgpiod(1), rgs(1), rgpio(3) diff --git a/lgpio.h b/lgpio.h new file mode 100644 index 0000000..8f50c8c --- /dev/null +++ b/lgpio.h @@ -0,0 +1,2947 @@ +/* +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 +*/ + +#ifndef LGPIO_H +#define LGPIO_H + +#include +#include +#include +#include + +#define LGPIO_VERSION 0x00000000 + +#define LG_CD "LG_CD" /* configuration directory */ +#define LG_WD "LG_WD" /* working directory */ + +/*TEXT + +lgpio is a C library for Linux Single Board Computers which +allows control of the General Purpose Input Output pins. + +*Features* + +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 a simple interface to start and stop new threads + +*Usage* + +Include in your source files. + +Assuming your source is in a single file called prog.c use the following +command to build and run the executable. + +. . +gcc -Wall -o prog prog.c -llgpio +./prog +. . + +For examples of usage see the C programs within the lg archive file. + +*Notes* + +All the functions which return an int return < 0 on error. + +TEXT*/ + +/*OVERVIEW + +GPIO + +lgGpiochipOpen Opens a gpiochip device +lgGpiochipClose Closes a gpiochip device + +lgGpioGetChipInfo Gets gpiochip information +lgGpioGetLineInfo Gets gpiochip line information +lgGpioGetMode Gets the mode of a GPIO + +lgGpioSetUser Notifies Linux of the GPIO user + +lgGpioClaimInput Claims a GPIO for input +lgGpioClaimOutput Claims a GPIO for output +lgGpioClaimAlert Claims a GPIO for alerts +lgGpioFree Frees a GPIO + +lgGroupClaimInput Claims a group of GPIO for inputs +lgGroupClaimOutput Claims a group of GPIO for outputs +lgGroupFree Frees a group of GPIO + +lgGpioRead Reads a GPIO +lgGpioWrite Writes a GPIO + +lgGroupRead Reads a group of GPIO +lgGroupWrite Writes a group of GPIO + +lgTxPulse Starts pulses on a GPIO +lgTxPwm Starts PWM pulses on a GPIO +lgTxServo Starts Servo pulses on a GPIO +lgTxWave Starts a wave on a group of GPIO +lgTxBusy See if tx is active on a GPIO or group +lgTxRoom See if more room for tx on a GPIO or group + +lgGpioSetDebounce Sets the debounce time for a GPIO +lgGpioSetWatchdog Sets the watchdog time for a GPIO + +lgGpioSetAlertsFunc Starts a GPIO callback +lgGpioSetSamplesFunc Starts a GPIO callback for all GPIO + +I2C + +lgI2cOpen Opens an I2C device +lgI2cClose Closes an I2C device + +lgI2cWriteQuick SMBus write quick + +lgI2cReadByte SMBus read byte +lgI2cWriteByte SMBus write byte + +lgI2cReadByteData SMBus read byte data +lgI2cWriteByteData SMBus write byte data + +lgI2cReadWordData SMBus read word data +lgI2cWriteWordData SMBus write word data + +lgI2cReadBlockData SMBus read block data +lgI2cWriteBlockData SMBus write block data + +lgI2cReadI2CBlockData SMBus read I2C block data +lgI2cWriteI2CBlockData SMBus write I2C block data + +lgI2cReadDevice Reads the raw I2C device +lgI2cWriteDevice Writes the raw I2C device + +lgI2cProcessCall SMBus process call +lgI2cBlockProcessCall SMBus block process call + +lgI2cSegments Performs multiple I2C transactions +lgI2cZip Performs multiple I2C transactions + +NOTIFICATIONS + +lgNotifyOpen Request a notification +lgNotifyClose Close a notification +lgNotifyPause Pause notifications +lgNotifyResume Start notifications + +SERIAL + +lgSerialOpen Opens a serial device +lgSerialClose Closes a serial device + +lgSerialReadByte Reads a byte from a serial device +lgSerialWriteByte Writes a byte to a serial device + +lgSerialRead Reads bytes from a serial device +lgSerialWrite Writes bytes to a serial device + +lgSerialDataAvailable Returns number of bytes ready to be read + +SPI + +lgSpiOpen Opens a SPI device +lgSpiClose Closes a SPI device + +lgSpiRead Reads bytes from a SPI device +lgSpiWrite Writes bytes to a SPI device + +lgSpiXfer Transfers bytes with a SPI device + +THREADS + +lgThreadStart Start a new thread +lgThreadStop Stop a previously started thread + +UTILITIES + +lguVersion Gets the library version +lguSbcName Gets the host name of the SBC + +lguGetInternal Get an internal configuration value +lguSetInternal Set an internal configuration value + +lguSleep Sleeps for a given time + +lguTimestamp Gets the current timestamp +lguTime Gets the current time + +lgErrStr Gets a text description of an error code + +lguSetWorkDir Set the working directory +lguGetWorkDir Get the working directory + +OVERVIEW*/ + +#ifdef __cplusplus +extern "C" { +#endif + +#if __BIG_ENDIAN__ + +#define htonll(x) (x) +#define ntohll(x) (x) + +#else + +#define htonll(x) (((uint64_t)htonl((x)&0xffffffff)<<32)|htonl((x)>>32)) +#define ntohll(x) (((uint64_t)ntohl((x)&0xffffffff)<<32)|ntohl((x)>>32)) + +#endif + +#define LG_CFG_ID_DEBUG_LEVEL 0 +#define LG_CFG_ID_MIN_DELAY 1 + +#define LG_MAX_PATH 1024 + +#define LG_THREAD_NONE 0 +#define LG_THREAD_STARTED 1 +#define LG_THREAD_RUNNING 2 + +#define LG_NOTIFY_CLOSED 0 +#define LG_NOTIFY_CLOSING 1 +#define LG_NOTIFY_RUNNING 2 +#define LG_NOTIFY_PAUSED 3 + +#define MAX_REPORT 250 +#define MAX_SAMPLE 4000 + +#define MAX_EMITS (PIPE_BUF / sizeof(lgGpioReport_t)) + +#define STACK_SIZE (256*1024) + +#define LG_USER_LEN 16 +#define LG_SALT_LEN 16 + +/* File constants +*/ + +#define LG_FILE_NONE 0 +#define LG_FILE_MIN 1 +#define LG_FILE_READ 1 +#define LG_FILE_WRITE 2 +#define LG_FILE_RW 3 +#define LG_FILE_APPEND 4 +#define LG_FILE_CREATE 8 +#define LG_FILE_TRUNC 16 +#define LG_FILE_MAX 31 + +#define LG_FROM_START 0 +#define LG_FROM_CURRENT 1 +#define LG_FROM_END 2 + +/* GPIO constants +*/ + +#define LG_GPIO_LABEL_LEN 32 +#define LG_GPIO_NAME_LEN 32 +#define LG_GPIO_USER_LEN 32 + +#define LG_GPIO_IS_KERNEL GPIOLINE_FLAG_KERNEL +#define LG_GPIO_IS_OUT GPIOLINE_FLAG_IS_OUT +#define LG_GPIO_IS_ACTIVE_LOW GPIOLINE_FLAG_ACTIVE_LOW +#define LG_GPIO_IS_OPEN_DRAIN GPIOLINE_FLAG_OPEN_DRAIN +#define LG_GPIO_IS_OPEN_SOURCE GPIOLINE_FLAG_OPEN_SOURCE + +#define LG_SET_INPUT GPIOHANDLE_REQUEST_INPUT +#define LG_SET_OUTPUT GPIOHANDLE_REQUEST_OUTPUT +#define LG_SET_ACTIVE_LOW GPIOHANDLE_REQUEST_ACTIVE_LOW +#define LG_SET_OPEN_DRAIN GPIOHANDLE_REQUEST_OPEN_DRAIN +#define LG_SET_OPEN_SOURCE GPIOHANDLE_REQUEST_OPEN_SOURCE + +#define LG_RISING_EDGE GPIOEVENT_REQUEST_RISING_EDGE +#define LG_FALLING_EDGE GPIOEVENT_REQUEST_FALLING_EDGE +#define LG_BOTH_EDGES GPIOEVENT_REQUEST_BOTH_EDGES + +#define LG_LOW 0 +#define LG_HIGH 1 +#define LG_TIMEOUT 2 + +#define LG_TX_PWM 0 +#define LG_TX_WAVE 1 + +#define LG_MAX_MICS_DEBOUNCE 5000000 /* 5 seconds */ +#define LG_MAX_MICS_WATCHDOG 300000000 /* 5 minutes */ + +/* Script constants +*/ + +#define LG_MAX_MICS_DELAY 5e6 /* 5 seconds */ +#define LG_MAX_MILS_DELAY 300e6 /* 5 minutes */ + +/* script status +*/ + +#define LG_SCRIPT_INITING 0 +#define LG_SCRIPT_READY 1 +#define LG_SCRIPT_RUNNING 2 +#define LG_SCRIPT_WAITING 3 +#define LG_SCRIPT_EXITED 4 +#define LG_SCRIPT_ENDED 5 +#define LG_SCRIPT_HALTED 6 +#define LG_SCRIPT_FAILED 7 + +/* SPI constants +*/ + +#define LG_MAX_SPI_DEVICE_COUNT (1<<16) + +/* I2C constants +*/ + +/* max lgI2cMsg_t per transaction */ + +#define LG_I2C_RDRW_IOCTL_MAX_MSGS 42 + +#define LG_MAX_I2C_DEVICE_COUNT (1<<16) +#define LG_MAX_I2C_ADDR 0x7F + +/* i2cZip commands */ + +#define LG_I2C_END 0 +#define LG_I2C_ESC 1 +#define LG_I2C_ADDR 2 +#define LG_I2C_FLAGS 3 +#define LG_I2C_READ 4 +#define LG_I2C_WRITE 5 + +/* types +*/ + +typedef struct lgChipInfo_s +{ + uint32_t lines; + char name[LG_GPIO_NAME_LEN]; /* Linux name */ + char label[LG_GPIO_LABEL_LEN]; /* functional name */ +} lgChipInfo_t, *lgChipInfo_p; + +typedef struct +{ + uint16_t state; + int fd; + int pipe_number; + int max_emits; +} lgNotify_t; + +typedef void (*callbk_t) (); + +typedef struct +{ + uint64_t timestamp; /* alert time in nanoseconds*/ + uint8_t chip; /* gpiochip device number */ + uint8_t gpio; /* offset into gpio device */ + uint8_t level; /* 0=low, 1=high, 2=watchdog */ + uint8_t flags; /* none defined, ignore report if non-zero */ +} lgGpioReport_t; + +typedef struct lgGpioAlert_s +{ + lgGpioReport_t report; + int nfyHandle; +} lgGpioAlert_t, *lgGpioAlert_p; + +typedef struct lgLineInfo_s +{ + uint32_t offset; /* GPIO number */ + uint32_t lFlags; + char name[LG_GPIO_NAME_LEN]; /* GPIO name */ + char user[LG_GPIO_USER_LEN]; /* user */ +} lgLineInfo_t, *lgLineInfo_p; + +typedef struct lgPulse_s +{ + uint64_t bits; + uint64_t mask; + int64_t delay; +} lgPulse_t, *lgPulse_p; + +typedef struct +{ + uint16_t addr; /* slave address */ + uint16_t flags; + uint16_t len; /* msg length */ + uint8_t *buf; /* pointer to msg data */ +} lgI2cMsg_t; + + + +typedef void (*lgGpioAlertsFunc_t) (int num_alerts, + lgGpioAlert_p alerts, + void *userdata); + +typedef void *(lgThreadFunc_t) (void *); + + +/* semi-private prototypes +*/ + +const char *lguGetConfigDir(void); +void lguSetConfigDir(const char *dirPath); +int lgGpioSetBannedState(int handle, int gpio, int banned); + +/* GPIO chip API +*/ + +/*F*/ +int lgGpiochipOpen(int gpioDev); +/*D +This returns a handle to a gpiochip device. + +. . +gpioDev: >= 0 +. . + +If OK returns a handle (>= 0). + +On failure returns a negative error code. + +... +h = lgGpiochipOpen(0); // open /dev/gpiochip0 + +if (h >= 0) +{ + // open ok +} +else +{ + // open error +} +... +D*/ + +/*F*/ +int lgGpiochipClose(int handle); +/*D +This closes an opened gpiochip device. + +. . +handle: >= 0 (as returned by [*lgGpiochipOpen*]) +. . + +If OK returns 0. + +On failure returns a negative error code. + +... +status = lgGpiochipClose(h); // close gpiochip + +if (status < 0) +{ + // close failed +} +... +D*/ + +/*F*/ +int lgGpioGetChipInfo(int handle, lgChipInfo_p chipInfo); +/*D +This returns information about a gpiochip. + +. . + handle: >= 0 (as returned by [*lgGpiochipOpen*]) +chipInfo: A pointer to space for a lgChipInfo_t object +. . + +If OK returns 0 and updates chipInfo. + +On failure returns a negative error code. + +This command gets the number of GPIO on the gpiochip, +its name, and its usage. + +... +lgChipInfo_t cInfo; + +status = lgGpioGetChipInfo(h, &cInfo); + +if (status == LG_OKAY) +{ + printf("lines=%d name=%s label=%s\n", + cInfo.lines, cInfo.name, cInfo.label)) +} +... +D*/ + + +/*F*/ +int lgGpioGetLineInfo(int handle, int gpio, lgLineInfo_p lineInfo); +/*D +Returns information about a GPIO. + +. . + handle: >= 0 (as returned by [*lgGpiochipOpen*]) + gpio: >= 0, as legal for the gpiochip +lineInfo: A pointer to space for a lgLineInfo_t object +. . + +If OK returns 0 and updates lineInfo. + +On failure returns a negative error code. + +This command gets information for a GPIO of a gpiochip. +In particular it gets the GPIO number, kernel usage flags, +its user, and its purpose. + +The usage flags are bits. + +Bit @ value @ Bit meaning +0 @ 1 @ GPIO in use by the kernel +1 @ 2 @ GPIO is an output +2 @ 4 @ GPIO is active low +3 @ 8 @ GPIO is open drain +4 @ 16 @ GPIO is open source + +The user and purpose fields are filled in by the software which has +claimed the GPIO and may be blank. + +... +lgLineInfo_t lInfo; + +status = lgGpioGetLineInfo(h, gpio, &lInfo); + +if (status == LG_OKAY) +{ + printf("lFlags=%d name=%s user=%s\n", + lInfo.lines, lInfo.name, lInfo.user)) +} +... +D*/ + + +/*F*/ +int lgGpioGetMode(int handle, int gpio); +/*D +Returns the GPIO mode. + +. . + handle: >= 0 (as returned by [*lgGpiochipOpen*]) + gpio: >= 0, as legal for the gpiochip +. . + +If OK returns the GPIO mode. + +On failure returns a negative error code. + +Mode 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: --- +6 @ 64 @ Kernel: --- +7 @ 128 @ Kernel: --- +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: --- +D*/ + + +/*F*/ +int lgGpioSetUser(int handle, const char *gpiouser); +/*D +This sets the user string to be associated with each claimed GPIO. + +. . + handle: >= 0 (as returned by [*lgGpiochipOpen*]) +gpiouser: a string up to 32 characters long +. . + +If OK returns 0. + +On failure returns a negative error code. + +... +status = lgGpioSetUser(h, "my_title"); +... +D*/ + + +/*F*/ +int lgGpioClaimInput(int handle, int lFlags, int gpio); +/*D +This claims a GPIO for input. + +. . +handle: >= 0 (as returned by [*lgGpiochipOpen*]) +lFlags: line flags for the GPIO + gpio: the GPIO to be claimed +. . + +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, or open source. + +... +// open GPIO 23 for input +status = lgGpioClaimInput(h, 0, 23); +... +D*/ + + +/*F*/ +int lgGpioClaimOutput(int handle, int lFlags, int gpio, int level); +/*D +This claims a GPIO for output. +. . +handle: >= 0 (as returned by [*lgGpiochipOpen*]) +lFlags: line flags for the GPIO + gpio: the GPIO to be claimed + level: the initial level to set 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, or open source. + +If level is zero the GPIO will be initialised low. If any other +value is used the GPIO will be initialised high. + +... +// open GPIO 31 for high output +status = lgGpioClaimOutput(h, 0, 31, 1); +... +D*/ + + +/*F*/ +int lgGpioClaimAlert( + int handle, int lFlags, int eFlags, int gpio, int nfyHandle); +/*D +This claims a GPIO for alerts on level changes. + +. . + handle: >= 0 (as returned by [*lgGpiochipOpen*]) + lFlags: line flags for the GPIO + eFlags: event flags for the GPIO + gpio: >= 0, as legal for the gpiochip +nfyHandle: >= 0 (as returned by [*lgNotifyOpen*]) +. . + +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, or open source. + +The event flags are used to specify alerts for a rising edge, +falling edge, or both edges. + +The alerts will be sent to a previously opened notification. If +you don't want them sent to a notification set nfyHandle to -1. + +The alerts will also be sent to any callback registered for the +GPIO by [*lgGpioSetAlertsFunc*]. + +All GPIO alerts are also sent to a callback registered by +[*lgGpioSetSamplesFunc*]. + +... +status = lgGpioClaimAlert(h, 0, LG_BOTH_EDGES, 16, -1); +... +D*/ + +/*F*/ +int lgGpioFree(int handle, int gpio); +/*D +This frees a GPIO. + +. . +handle: >= 0 (as returned by [*lgGpiochipOpen*]) + 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. + +... +status = lgGpioFree(h, 16); +... +D*/ + + +/*F*/ +int lgGroupClaimInput( + int handle, int lFlags, int count, const int *gpios); +/*D +This claims a group of GPIO for inputs. + +. . +handle: >= 0 (as returned by [*lgGpiochipOpen*]) +lFlags: line flags for the GPIO group + count: the number of GPIO to claim + gpios: the group 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, or open source. + +gpios is an array of one or more GPIO. The first GPIO is +called the group leader and is used to reference the group as a whole. + +... +int buttons[4] = {9, 7, 2, 6}; + +status = lgGroupClaimInput(h, 0, 4, buttons); + +if (status == LG_OKAY) +{ + // OK +} +else +{ + // Error +} +... +D*/ + + +/*F*/ +int lgGroupClaimOutput( + int handle, int lFlags, int count, const int *gpios, const int *levels); +/*D +This claims a group of GPIO for outputs. + +. . +handle: >= 0 (as returned by [*lgGpiochipOpen*]) +lFlags: line flags for the GPIO group + count: the number of GPIO to claim + gpios: the group GPIO +levels: the initial level for each 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, or open source. + +gpios is an array of one or more GPIO. The first GPIO is +called the group leader and is used to reference the group as a whole. + +levels is an array of initialisation values for the GPIO. If a value is +zero the corresponding GPIO will be initialised low. If any other +value is used the corresponding GPIO will be initialised high. + +... +int leds[7] = {15, 16, 17, 8, 12, 13, 14}; +int levels[7] = { 1, 0, 1, 1, 1, 0, 0}; + +status = lgGroupClaimInput(h, 0, 7, leds, levels); + +if (status == LG_OKAY) +{ + // OK +} +else +{ + // Error +} +... +D*/ + +/*F*/ +int lgGroupFree(int handle, int gpio); +/*D +This frees all the GPIO associated with a group. + +. . +handle: >= 0 (as returned by [*lgGpiochipOpen*]) + gpio: the group 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. + +... +status = lgGroupFree(9); // free buttons +... +D*/ + + +/*F*/ +int lgGpioRead(int handle, int gpio); +/*D +This returns the level of a GPIO. + +. . +handle: >= 0 (as returned by [*lgGpiochipOpen*]) + 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. + +... +level = lgGpioRead(h, 15); // get level of GPIO 15 +... +D*/ + + +/*F*/ +int lgGpioWrite(int handle, int gpio, int level); +/*D +This sets the level of an output GPIO. + +. . +handle: >= 0 (as returned by [*lgGpiochipOpen*]) + gpio: the GPIO to be written + level: the level to set +. . + +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). + +... +status = lgGpioWrite(h, 23, 1); // set GPIO 23 high +... +D*/ + + +/*F*/ +int lgGroupRead(int handle, int gpio, uint64_t *groupBits); +/*D +This returns the levels read from a group. + +. . + handle: >= 0 (as returned by [*lgGpiochipOpen*]) + gpio: the group to be read +groupBits: a pointer to a 64-bit memory area for the returned levels +. . + +If OK returns the group size and updates groupBits. + +On failure returns a negative error code. + +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 groupBits 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. + +... +// assuming a read group of 4 buttons: 9, 7, 2, 6. +uint64_t bits; + +size = lgGroupRead(h, 9, &bits); // 9 is buttons group leader + +if (size >= 0) // size of group is returned so size will be 4 +{ + level_9 = (bits >> 0) & 1; + level_7 = (bits >> 1) & 1; + level_2 = (bits >> 2) & 1; + level_6 = (bits >> 3) & 1; +} +else +{ + // error +} +... + +D*/ + + +/*F*/ +int lgGroupWrite( + int handle, int gpio, uint64_t groupBits, uint64_t groupMask); +/*D +This sets the levels of an output group. + +. . + handle: >= 0 (as returned by [*lgGpiochipOpen*]) + gpio: the group to be written +groupBits: the level to set if the corresponding bit in groupMask is set +groupMask: 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 groupBits. + +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 modified by the groupMask. A GPIO is only +updated if the corresponding bit in the mask is 1. + +... +// assuming an output group of 7 LEDs: 15, 16, 17, 8, 12, 13, 14. + +// switch on all LEDs +status = lgGroupWrite(h, 15, 0x7f, 0x7f); + +// switch off all LEDs +status = lgGroupWrite(h, 15, 0x00, 0x7f); + +// switch on first 4 LEDs, leave others unaltered +status = lgGroupWrite(h, 15, 0x0f, 0x0f); + +// switch on LED attached to GPIO 13, leave others unaltered +status = lgGroupWrite(h, 15, 32, 32); +... +D*/ + + +/*F*/ +int lgTxPulse( + int handle, + int gpio, + int pulseOn, + int pulseOff, + int pulseOffset, + int pulseCycles); +/*D +This starts software timed pulses on an output GPIO. + +. . + handle: >= 0 (as returned by [*lgGpiochipOpen*]) + gpio: the GPIO to be written + pulseOn: pulse high time in microseconds + pulseOff: pulse low time in microseconds +pulseOffset: offset from nominal pulse start position +pulseCycles: the number of pulses 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 pulseOn and pulseOff 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. + +pulseCycles cycles are transmitted (0 means infinite). Each cycle +consists of pulseOn microseconds of GPIO high followed by pulseOff +microseconds of GPIO low. + +PWM is characterised by two values, its frequency (number of cycles +per second) and its duty cycle (percentage of high time per cycle). + +The set frequency will be 1000000 / (pulseOn + pulseOff) Hz. + +The set duty cycle will be pulseOn / (pulseOn + pulseOff) * 100 %. + +E.g. if pulseOn is 50 and pulseOff is 100 the frequency will be 6666.67 Hz +and the duty cycle will be 33.33 %. + +pulseOffset is a microsecond offset from the natural start of the PWM 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 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 pulse settings may be queued in this way. + +... +slots_left = lgTxPulse(h, 8, 100000, 100000, 0, 0); // flash LED at 5 Hz + +slots_left = lgTxPulse(h, 30, 1500, 18500, 0, 0); // move servo to centre + +slots_left = lgTxPulse(h, 30, 2000, 18000, 0, 0); // move servo clockwise +... +D*/ + +/*F*/ +int lgTxPwm( + int handle, + int gpio, + float pwmFrequency, + float pwmDutyCycle, + int pwmOffset, + int pwmCycles); +/*D +This starts software timed PWM on an output GPIO. + +. . + handle: >= 0 (as returned by [*lgGpiochipOpen*]) + gpio: the GPIO to be pulsed +pwmFrequency: PWM frequency in Hz (0=off, 0.1-10000) +pwmDutyCycle: PWM duty cycle in % (0-100) + pwmOffset: offset from nominal pulse start position + pwmCycles: the number of pulses 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. + +PWM is characterised by two values, its frequency (number of cycles +per second) and its duty cycle (percentage of high time per cycle). + +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 PWM settings may be queued in this way. +D*/ + +/*F*/ +int lgTxServo( + int handle, + int gpio, + int pulseWidth, + int servoFrequency, + int servoOffset, + int servoCycles); +/*D +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 [*lgGpiochipOpen*]) + gpio: the GPIO to be pulsed + pulseWidth: pulse high time in microseconds (0=off, 500-2500) +servoFrequency: the number of pulses per second (40-500). + servoOffset: offset from nominal pulse start position + servoCycles: the number of pulses 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. + +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. +D*/ + + +/*F*/ +int lgTxWave( + int handle, int gpio, int count, lgPulse_p pulses); +/*D +This starts a wave on an output group of GPIO. + +. . +handle: >= 0 (as returned by [*lgGpiochipOpen*]) + gpio: the group leader + count: the number of pulses in the wave +pulses: the pulses +. . + +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 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. + +... +#include + +#include + +#define PULSES 2000 + +int main(int argc, char *argv[]) +{ + int GPIO[] = {16, 17, 18, 19, 20, 21}; + int levels[] = { 1, 1, 1, 1, 1, 1}; + int h; + int e; + int mask; + int delay; + int p; + lgPulse_t pulses[PULSES]; + + h = lgGpiochipOpen(0); // open /dev/gpiochip0 + + if (h < 0) { printf("ERROR: %s (%d)\n", lgErrStr(h), h); return 1; } + + e = lgGroupClaimOutput(h, 0, 6, GPIO, levels); + + if (e < 0) { printf("ERROR: %s (%d)\n", lgErrStr(e), e); return 1; } + + mask = 0; + p = 0; + + for (p=0; p>2; // see what sort of pattern we get + pulses[p].mask = mask; // with bits and mask changing + pulses[p].delay = (PULSES + 500) - p; + + if (++mask > 0x3f) mask = 0; + } + + lgTxWave(h, GPIO[0], p, pulses); + + while (lgTxBusy(h, GPIO[0], LG_TX_WAVE)) lguSleep(0.1); + + lgGpiochipClose(h); +} +... +D*/ + +/*F*/ +int lgTxBusy(int handle, int gpio, int kind); +/*D +This returns true if transmissions of the specified kind +are active on the GPIO or group. + +. . +handle: >= 0 (as returned by [*lgGpiochipOpen*]) + gpio: the gpio or group to be checked + kind: LG_TX_PWM or LG_TX_WAVE +. . + +If OK returns 1 for busy and 0 for not busy. + +On failure returns a negative error code. + +... +while (lgTxBusy(h, 15, LG_TX_PWM)) // wait for PWM to finish on GPIO 15 + lguSleep(0.1); +... +D*/ + +/*F*/ +int lgTxRoom(int handle, int gpio, int kind); +/*D +This returns the number of entries available for queueing +transmissions of the specified kind on the GPIO or group. + +. . +handle: >= 0 (as returned by [*lgGpiochipOpen*]) + gpio: the gpio or group to be checked + kind: LG_TX_PWM or LG_TX_WAVE +. . + +If OK returns the number of free entries (0 if none). + +On failure returns a negative error code. + +... +while (lgTxRoom(h, 17, LG_TX_WAVE) > 0)) +{ + // queue another wave +} +... +D*/ + +/*F*/ +int lgGpioSetDebounce(int handle, int gpio, int debounce_us); +/*D +This sets the debounce time for a GPIO. + +. . + handle: >= 0 (as returned by [*lgGpiochipOpen*]) + gpio: the GPIO to be configured +debounce_us: the debounce time in microseconds +. . + +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. + +... +lgSetDebounceTime(h, 16, 1000); // set a millisecond of debounce +... +D*/ + +/*F*/ +int lgGpioSetWatchdog(int handle, int gpio, int watchdog_us); +/*D +This sets the watchdog time for a GPIO. + +. . + handle: >= 0 (as returned by [*lgGpiochipOpen*]) + gpio: the GPIO to be configured +watchdog_us: the watchdog time in microseconds +. . + +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 LG_TIMEOUT (2) for a watchdog alert. + +... +lgSetWatchdogTime(h, 17, 200000); // alert if nothing for 0.2 seconds +... +D*/ + +/*F*/ +int lgGpioSetAlertsFunc( + int handle, int gpio, lgGpioAlertsFunc_t cbf, void *userdata); +/*D +This sets up a callback to be called when an alert +GPIO changes state. + +. . + handle: >= 0 (as returned by [*lgGpiochipOpen*]) + gpio: the GPIO to be monitored + cbf: the callback function +userdata: a pointer to arbitrary user data +. . + +If OK returns 0. + +On failure returns a negative error code. + +... +#include +#include + +#include + +void afunc(int e, lgGpioAlert_p evt, void *data) +{ + int i; + int userdata = *(int*)data; + + for (i=0; i +#include + +#include + +void afunc(int e, lgGpioAlert_p evt, void *data) +{ + int i; + int userdata = *(int*)data; + + for (i=0; i= 0). + +On failure returns a negative error code. + +A notification is a method for being notified of GPIO state changes +via a pipe or socket. + +The notification pipes are created in the library working directory +(see [*lguGetWorkDir*]). + +Pipe notifications for handle x will be available at the pipe +named lgd-nfy* (where * is the handle number). E.g. if the +function returns 15 then the notifications must be read +from lgd-nfy15. + +Socket notifications are returned to the socket which requested the +handle. + +... +h = lgNotifyOpen(); + +if (h >= 0) +{ + sprintf(str, "lgd-nfy%d", h); + + fd = open(str, O_RDONLY); + + if (fd >= 0) + { + // Okay. + } + else + { + // Error. + } +} +else +{ + // Error. +} +... +D*/ + + +/*F*/ +int lgNotifyResume(int handle); +/*D +This function restarts notifications on a paused notification. + +. . +handle: >= 0 (as returned by [*lgNotifyOpen*]) +. . + +If OK returns 0. + +On failure returns a negative error code. + +The notification gets state changes for each associated GPIO. + +Each notification occupies 16 bytes in the fifo and has the +following structure. + +. . +typedef struct +{ + uint64_t timestamp; // alert time in nanoseconds + uint8_t chip; // gpiochip device number + uint8_t gpio; // offset into gpio device + uint8_t level; // 0=low, 1=high, 2=timeout + uint8_t flags; // none currently defined +} lgGpioReport_t; +. . + +timestamp: the number of nanoseconds since the epoch (start of 1970) +level: indicates the level of the GPIO +flags: no flags are currently defined + +For future proofing it is probably best to ignore any notification +with non-zero flags. + +... +// Start notifications for associated GPIO. +lgNotifyResume(h); +... +D*/ + + +/*F*/ +int lgNotifyPause(int handle); +/*D +This function pauses notifications. + +. . +handle: >= 0 (as returned by [*lgNotifyOpen*]) +. . + +If OK returns 0. + +On failure returns a negative error code. + +Notifications are suspended until [*lgNotifyResume*] is called. + +... +lgNotifyPause(h); +... +D*/ + + +/*F*/ +int lgNotifyClose(int handle); +/*D +This function stops notifications and frees the handle for reuse. + +. . +handle: >= 0 (as returned by [*lgNotifyOpen*]) +. . + +If OK returns 0. + +On failure returns a negative error code. + +... +lgNotifyClose(h); +... +D*/ + + +/* I2C API +*/ + +/*F*/ +int lgI2cOpen(int i2cDev, int i2cAddr, int i2cFlags); +/*D +This returns a handle for the device at the address on the I2C bus. + +. . + i2cDev: >= 0 + i2cAddr: 0-0x7F +i2cFlags: 0 +. . + +If OK returns a handle (>= 0). + +On failure returns a negative error code. + +No flags are currently defined. This parameter should be set to zero. + +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 +i2cReg (8 bits): Command byte, a 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 +. . +D*/ + + +/*F*/ +int lgI2cClose(int handle); +/*D +This closes the I2C device. + +. . +handle: >= 0 (as returned by [*lgI2cOpen*]) +. . + +If OK returns 0. + +On failure returns a negative error code. +D*/ + + +/*F*/ +int lgI2cWriteQuick(int handle, int bitVal); +/*D +This sends a single bit (in the Rd/Wr bit) to the device. + +. . +handle: >= 0 (as returned by [*lgI2cOpen*]) +bitVal: 0-1, the value to write +. . + +If OK returns 0. + +On failure returns a negative error code. + +Quick command. SMBus 2.0 5.5.1 +. . +S Addr bit [A] P +. . +D*/ + + +/*F*/ +int lgI2cWriteByte(int handle, int byteVal); +/*D +This sends a single byte to the device. + +. . + handle: >= 0 (as returned by [*lgI2cOpen*]) +byteVal: 0-0xFF, the value to write +. . + +If OK returns 0. + +On failure returns a negative error code. + +Send byte. SMBus 2.0 5.5.2 +. . +S Addr Wr [A] bVal [A] P +. . +D*/ + + +/*F*/ +int lgI2cReadByte(int handle); +/*D +This reads a single byte from the device. + +. . +handle: >= 0 (as returned by [*lgI2cOpen*]) +. . + +If OK returns the byte read (0-255). + +On failure returns a negative error code. + +Receive byte. SMBus 2.0 5.5.3 +. . +S Addr Rd [A] [Data] NA P +. . +D*/ + + +/*F*/ +int lgI2cWriteByteData(int handle, int i2cReg, int byteVal); +/*D +This writes a single byte to the specified register of the device. + +. . + handle: >= 0 (as returned by [*lgI2cOpen*]) + i2cReg: 0-255, the register to write +byteVal: 0-0xFF, the value to write +. . + +If OK returns 0. + +On failure returns a negative error code. + +Write byte. SMBus 2.0 5.5.4 +. . +S Addr Wr [A] i2cReg [A] bVal [A] P +. . +D*/ + + +/*F*/ +int lgI2cWriteWordData(int handle, int i2cReg, int wordVal); +/*D +This writes a single 16 bit word to the specified register of the device. + +. . + handle: >= 0 (as returned by [*lgI2cOpen*]) + i2cReg: 0-255, the register to write +wordVal: 0-0xFFFF, the value to write +. . + +If OK returns 0. + +On failure returns a negative error code. + +Write word. SMBus 2.0 5.5.4 +. . +S Addr Wr [A] i2cReg [A] wValLow [A] wValHigh [A] P +. . +D*/ + + +/*F*/ +int lgI2cReadByteData(int handle, int i2cReg); +/*D +This reads a single byte from the specified register of the device. + +. . +handle: >= 0 (as returned by [*lgI2cOpen*]) +i2cReg: 0-255, the register to read +. . + +If OK returns the byte read (0-255). + +On failure returns a negative error code. + +Read byte. SMBus 2.0 5.5.5 +. . +S Addr Wr [A] i2cReg [A] S Addr Rd [A] [Data] NA P +. . +D*/ + + +/*F*/ +int lgI2cReadWordData(int handle, int i2cReg); +/*D +This reads a single 16 bit word from the specified register of the device. + +. . +handle: >= 0 (as returned by [*lgI2cOpen*]) +i2cReg: 0-255, the register to read +. . + +If OK returns the word read (0-65535). + +On failure returns a negative error code. + +Read word. SMBus 2.0 5.5.5 +. . +S Addr Wr [A] i2cReg [A] S Addr Rd [A] [DataLow] A [DataHigh] NA P +. . +D*/ + + +/*F*/ +int lgI2cProcessCall(int handle, int i2cReg, int wordVal); +/*D +This 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 [*lgI2cOpen*]) + i2cReg: 0-255, the register to write/read +wordVal: 0-0xFFFF, the value to write +. . + +If OK returns the word read (0-65535). + +On failure returns a negative error code. + +Process call. SMBus 2.0 5.5.6 +. . +S Addr Wr [A] i2cReg [A] wValLow [A] wValHigh [A] + S Addr Rd [A] [DataLow] A [DataHigh] NA P +. . +D*/ + + +/*F*/ +int lgI2cWriteBlockData(int handle, int i2cReg, const char *txBuf, int count); +/*D +This writes up to 32 bytes to the specified register of the device. + +. . +handle: >= 0 (as returned by [*lgI2cOpen*]) +i2cReg: 0-255, the register to write + txBuf: an array with the data to send + count: 1-32, the number of bytes to write +. . + +If OK returns 0. + +On failure returns a negative error code. + +Block write. SMBus 2.0 5.5.7 +. . +S Addr Wr [A] i2cReg [A] count [A] + txBuf0 [A] txBuf1 [A] ... [A] txBufn [A] P +. . +D*/ + + +/*F*/ +int lgI2cReadBlockData(int handle, int i2cReg, char *rxBuf); +/*D +This reads a block of up to 32 bytes from the specified register of +the device. + +. . +handle: >= 0 (as returned by [*lgI2cOpen*]) +i2cReg: 0-255, the register to read + rxBuf: an array to receive the read data +. . + +The amount of returned data is set by the device. + +If OK returns the count of bytes read (0-32) and updates rxBuf. + +On failure returns a negative error code. + +Block read. SMBus 2.0 5.5.7 +. . +S Addr Wr [A] i2cReg [A] + S Addr Rd [A] [Count] A [rxBuf0] A [rxBuf1] A ... A [rxBufn] NA P +. . +D*/ + + +/*F*/ +int lgI2cBlockProcessCall( + int handle, int i2cReg, char *ioBuf, int count); +/*D +This 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 [*lgI2cOpen*]) +i2cReg: 0-255, the register to write/read + ioBuf: an array with the data to send and to receive the read data + count: 1-32, the number of bytes to write +. . + +If OK returns the count of bytes read (0-32) and updates ioBuf. + +On failure returns a negative error code. + +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. + +Block write-block read. SMBus 2.0 5.5.8 +. . +S Addr Wr [A] i2cReg [A] count [A] ioBuf0 [A] ... ioBufn [A] + S Addr Rd [A] [Count] A [ioBuf0] A ... [ioBufn] A P +. . +D*/ + + +/*F*/ +int lgI2cReadI2CBlockData(int handle, int i2cReg, char *rxBuf, int count); +/*D +This reads count bytes from the specified register of the device. +The count may be 1-32. + +. . +handle: >= 0 (as returned by [*lgI2cOpen*]) +i2cReg: 0-255, the register to read + rxBuf: an array to receive the read data + count: 1-32, the number of bytes to read +. . + +If OK returns the count of bytes read (0-32) and updates rxBuf. + +On failure returns a negative error code. + +. . +S Addr Wr [A] i2cReg [A] + S Addr Rd [A] [rxBuf0] A [rxBuf1] A ... A [rxBufn] NA P +. . +D*/ + + +/*F*/ +int lgI2cWriteI2CBlockData(int handle, int i2cReg, const char *txBuf, int count); +/*D +This writes 1 to 32 bytes to the specified register of the device. + +. . +handle: >= 0 (as returned by [*lgI2cOpen*]) +i2cReg: 0-255, the register to write + txBuf: the data to write + count: 1-32, the number of bytes to write +. . + +If OK returns 0. + +On failure returns a negative error code. + +. . +S Addr Wr [A] i2cReg [A] txBuf0 [A] txBuf1 [A] ... [A] txBufn [A] P +. . +D*/ + +/*F*/ +int lgI2cReadDevice(int handle, char *rxBuf, int count); +/*D +This reads count bytes from the raw device into rxBuf. + +. . +handle: >= 0 (as returned by [*lgI2cOpen*]) + rxBuf: an array to receive the read data bytes + count: >0, the number of bytes to read +. . + +If OK returns count (>0) and updates rxBuf. + +On failure returns a negative error code. + +. . +S Addr Rd [A] [rxBuf0] A [rxBuf1] A ... A [rxBufn] NA P +. . +D*/ + + +/*F*/ +int lgI2cWriteDevice(int handle, const char *txBuf, int count); +/*D +This writes count bytes from txBuf to the raw device. + +. . +handle: >= 0 (as returned by [*lgI2cOpen*]) + txBuf: an array containing the data bytes to write + count: >0, the number of bytes to write +. . + +If OK returns 0. + +On failure returns a negative error code. + +. . +S Addr Wr [A] txBuf0 [A] txBuf1 [A] ... [A] txBufn [A] P +. . +D*/ + +/*F*/ +int lgI2cSegments(int handle, lgI2cMsg_t *segs, int count); +/*D +This function executes multiple I2C segments in one transaction by +calling the I2C_RDWR ioctl. + +. . +handle: >= 0 (as returned by [*lgI2cOpen*]) + segs: an array of I2C segments + count: >0, the number of I2C segments +. . + +If OK returns the number of segments executed. + +On failure returns a negative error code. +D*/ + +/*F*/ +int lgI2cZip( + int handle, const char *txBuf, int txCount, char *rxBuf, int rxCount); +/*D +This function executes a sequence of I2C operations. The +operations to be performed are specified by the contents of txBuf +which contains the concatenated command codes and associated data. + +. . + handle: >= 0 (as returned by [*lgI2cOpen*]) + txBuf: pointer to the concatenated I2C commands, see below + txCount: size of command buffer + rxBuf: pointer to buffer to hold returned data + rxCount: size of receive buffer +. . + +If OK returns the count of bytes read (which may be 0) and updates rxBuf. + +On failure returns a negative error code. + +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. + +The returned I2C data is stored in consecutive locations of rxBuf. + +... +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 +... +D*/ + +/* Serial API +*/ + +/*F*/ +int lgSerialOpen(const char *serDev, int serBaud, int serFlags); +/*D +This function opens a serial device at a specified baud rate +and with specified flags. The device must be present in /dev. + +. . + serDev: the serial device to open + serBaud: the baud rate in bits per second, see below +serFlags: 0 +. . + +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. + +No flags are currently defined. This parameter should be set to zero. +D*/ + + +/*F*/ +int lgSerialClose(int handle); +/*D +This function closes the serial device. + +. . +handle: >= 0 (as returned by [*lgSerialOpen*]) +. . + +If OK returns 0. + +On failure returns a negative error code. +D*/ + +/*F*/ +int lgSerialWriteByte(int handle, int byteVal); +/*D +This function writes the byte to the serial device. + +. . + handle: >= 0 (as returned by [*lgSerialOpen*]) +byteVal: the byte to write. +. . + +If OK returns 0. + +On failure returns a negative error code. +D*/ + +/*F*/ +int lgSerialReadByte(int handle); +/*D +This function reads a byte from the serial device. + +. . +handle: >= 0 (as returned by [*lgSerialOpen*]) +. . + +If OK returns the byte read (0-255). + +On failure returns a negative error code. +D*/ + +/*F*/ +int lgSerialWrite(int handle, const char *txBuf, int count); +/*D +This function writes count bytes from txBuf to the the serial device. + +. . +handle: >= 0 (as returned by [*lgSerialOpen*]) + txBuf: the array of bytes to write + count: the number of bytes to write +. . + +If OK returns 0. + +On failure returns a negative error code. +D*/ + + +/*F*/ +int lgSerialRead(int handle, char *rxBuf, int count); +/*D +This function reads up count bytes from the the serial device +and writes them to rxBuf. + +. . +handle: >= 0 (as returned by [*lgSerialOpen*]) + rxBuf: an array to receive the read data + count: the maximum number of bytes to read +. . + +If OK returns the count of bytes read (>= 0) and updates rxBuf. + +On failure returns a negative error code. + +D*/ + + +/*F*/ +int lgSerialDataAvailable(int handle); +/*D +This function returns the count of bytes available +to be read from the device. + +. . +handle: >= 0 (as returned by [*lgSerialOpen*]) +. . + +If OK returns the count of bytes available(>= 0). + +On failure returns a negative error code. +D*/ + +/* SPI API +*/ + +/*F*/ +int lgSpiOpen(int spiDev, int spiChan, int spiBaud, int spiFlags); +/*D +This function returns a handle for the SPI device on the channel. + +. . + spiDev: >= 0 + spiChan: >= 0 + spiBaud: the SPI speed to set in bits per second +spiFlags: see below +. . + +If OK returns a handle (>= 0). + +On failure returns a negative error code. + +The flags may be used to modify the default behaviour. + +spiFlags 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. +D*/ + +/*F*/ +int lgSpiClose(int handle); +/*D +This functions closes the SPI device. + +. . +handle: >= 0 (as returned by [*lgSpiOpen*]) +. . + +If OK returns 0. + +On failure returns a negative error code. +D*/ + + +/*F*/ +int lgSpiRead(int handle, char *rxBuf, int count); +/*D +This function reads count bytes of data from the SPI +device. + +. . +handle: >= 0 (as returned by [*lgSpiOpen*]) + rxBuf: an array to receive the read data bytes + count: the number of bytes to read +. . + +If OK returns the count of bytes read and updates rxBuf. + +On failure returns a negative error code. +D*/ + + +/*F*/ +int lgSpiWrite(int handle, const char *txBuf, int count); +/*D +This function writes count bytes of data from txBuf to the SPI +device. + +. . +handle: >= 0 (as returned by [*lgSpiOpen*]) + txBuf: the data bytes to write + count: the number of bytes to write +. . + +If OK returns the count of bytes written. + +On failure returns a negative error code. +D*/ + +/*F*/ +int lgSpiXfer(int handle, const char *txBuf, char *rxBuf, int count); +/*D +This function transfers count bytes of data from txBuf to the SPI +device. Simultaneously count bytes of +data are read from the device and placed in rxBuf. + +. . +handle: >= 0 (as returned by [*lgSpiOpen*]) + txBuf: the data bytes to write + rxBuf: the received data bytes + count: the number of bytes to transfer +. . + +If OK returns the count of bytes transferred and updates rxBuf. + +On failure returns a negative error code. +D*/ + + +/* Threads API +*/ + +/*F*/ +pthread_t *lgThreadStart(lgThreadFunc_t f, void *userdata); +/*D +Starts a new thread of execution with f as the main routine. + +. . + f: the main function for the new thread +userdata: a pointer to arbitrary user data +. . + +If OK returns a pointer to a pthread_t. + +On failure returns NULL. + +The function is passed the single argument arg. + +The thread can be cancelled by passing the pointer to pthread_t to +[*lgThreadStop*]. + +... +#include +#include +#include + +void *myfunc(void *arg) +{ + while (1) + { + printf("%s\n", arg); + sleep(1); + } +} + +int main(int argc, char *argv[]) +{ + pthread_t *p1, *p2, *p3; + + p1 = lgThreadStart(myfunc, "thread 1"); sleep(3); + + p2 = lgThreadStart(myfunc, "thread 2"); sleep(3); + + p3 = lgThreadStart(myfunc, "thread 3"); sleep(3); + + lgThreadStop(p3); sleep(3); + + lgThreadStop(p2); sleep(3); + + lgThreadStop(p1); sleep(3); +} +... +D*/ + + +/*F*/ +void lgThreadStop(pthread_t *pth); +/*D +Cancels the thread pointed at by pth. + +. . +pth: a thread pointer (as returned by [*lgThreadStart*]) +. . + +No value is returned. + +The thread to be stopped should have been started with [*lgThreadStart*]. +D*/ + +/*F*/ +uint64_t lguTimestamp(void); +/*D +Returns the current timestamp. + +The timestamp is the number of nanoseconds since the epoch (start +of 1970). +D*/ + +/*F*/ +double lguTime(void); +/*D +Returns the current time. + +The time is the number of seconds since the epoch (start +of 1970). +D*/ + +/*F*/ +void lguSleep(double sleepSecs); +/*D +Sleeps for the specified number of seconds. + +. . +sleepSecs: how long to sleep in seconds +. . +D*/ + + +/*F*/ +int lguSbcName(char *rxBuf, int count); +/*D +Copies the host name of the machine running the lgpio library +to the supplied buffer. Up to count characters are copied. + +. . +rxBuf: a buffer to receive the host name +count: the size of the rxBuf +. . + +If OK returns the count of bytes copied and updates rxBuf. + +On failure returns a negative error code. +D*/ + +/*F*/ +int lguVersion(void); +/*D +Returns the lgpiolibrary version number. +D*/ + +/*F*/ +int lguGetInternal(int cfgId, uint64_t *cfgVal); +/*D +Get an internal configuration value. + +. . + cfgId: the item. +cfgVal: a variable to receive the returned value +. . + +If OK returns 0 and updates cfgVal. + +On failure returns a negative error code. +D*/ + +/*F*/ +int lguSetInternal(int cfgId, uint64_t cfgVal); +/*D +Set an internal configuration value. + +. . + cfgId: the item +cfgVal: the value to set +. . + +If OK returns 0. + +On failure returns a negative error code. +D*/ + +/*F*/ +const char *lgErrStr(int error); +/*D +Returns the error text for an error code. + +. . +error: the error code +. . +D*/ + +/*F*/ +void lguSetWorkDir(const char *dirPath); +/*D +Sets the library working directory. + +This function has no affect if the working directory has already +been set. + +. . +dirPath: the directory to set as the working directory +. . + +If dirPath does not start with a / the directory is relative to +the library launch directory. +D*/ + +/*F*/ +const char *lguGetWorkDir(void); +/*D +Returns the library working directory. +D*/ + +#ifdef __cplusplus +} +#endif + +/*PARAMS + +bitVal:: +A value of 0 or 1. + +byteVal:: 0-255 +An 8-bit byte value. + +cbf:: +An alerts callback function. + +cfgId:: +A number identifying a configuration item. + +. . +LG_CFG_ID_DEBUG_LEVEL 0 +LG_CFG_ID_MIN_DELAY 1 +. . + +cfgVal:: +The value of a configuration item. + +*cfgVal:: +The value of a configuration item. + +char:: +A single character, an 8 bit quantity able to store 0-255. + +chipInfo:: +A pointer to a lgChipInfo_t object. + +count:: +The number of items. + +debounce_us:: +The debounce time in microseconds. + +*dirPath:: +A directory path which. + +double:: +A floating point number. + +eFlags:: + +The type of GPIO edge to generate an alert. See [*lgGpioClaimAlert*]. + +. . +LG_RISING_EDGE +LG_FALLING_EDGE +LG_BOTH_EDGES +. . + +error:: +An error code. All error codes are negative. + +f:: +A function. + +float:: +A floating point number + +gpio:: +A GPIO number, the offset of the GPIO from the base of the gpiochip. +Offsets start at 0. + +gpioDev :: >= 0 +The device number of a gpiochip. + +*gpios:: +An array of GPIO numbers. + +*gpiouser:: +A string of up to 32 characters denoting the user of a GPIO. + +groupBits:: +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. + +*groupBits:: +A 64-bit value denoting the levels of a group. + +If bit x is set then GPIO x of the group is high. + +groupMask:: +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 + +[*lgGpiochipOpen*] +[*lgI2cOpen*] +[*lgNotifyOpen*] +[*lgSerialOpen*] +[*lgSpiOpen*] + +i2cAddr:: 0-0x7F +The address of a device on the I2C bus. + +i2cDev::>= 0 +An I2C device number. + +i2cFlags::0 +Flags which modify an I2C open command. None are currently defined. + +i2cReg:: 0-255 +A register of an I2C device. + +int:: +A whole number, negative or positive. + +*ioBuf:: +A pointer to a buffer used to hold data to send and the data received. + +kind:: LG_TX_PWM or LG_TX_WAVE +A type of transmission: PWM or wave. + +level:: +A GPIO level (0 or 1). + +*levels:: +An array of GPIO levels. + +lFlags:: + +line flags for the GPIO. + +The following values may be or'd to form the value. + +. . +LG_SET_ACTIVE_LOW +LG_SET_OPEN_DRAIN +LG_SET_OPEN_SOURCE +. . + +lgChipInfo_p:: +A pointer to a lgChipInfo_t object. + +. . +typedef struct lgChipInfo_s +{ + uint32_t lines; // number of GPIO + char name[LG_GPIO_NAME_LEN]; // Linux name + char label[LG_GPIO_LABEL_LEN]; // functional name +} lgChipInfo_t, *lgChipInfo_p; +. . + +lgGpioAlert_t:: + +. . +typedef struct lgGpioAlert_s +{ + lgGpioReport_t report; + int nfyHandle; +} lgGpioAlert_t, *lgGpioAlert_p; +. . + +See [*lgGpioReport_t*]. + + +lgGpioAlertsFunc_t:: + +. . +typedef void (*lgGpioAlertsFunc_t) + (int num_alerts, lgGpioAlert_p alerts, void *userdata); +. . + +See [*lgGpioAlert_t*]. + +lgGpioReport_t:: + +. . +typedef struct +{ + uint64_t timestamp; // alert time in nanoseconds + uint8_t chip; // gpiochip device number + uint8_t gpio; // offset into gpio device + uint8_t level; // 0=low, 1=high, 2=watchdog + uint8_t flags; // none defined, ignore report if non-zero +} lgGpioReport_t; +. . + +lgI2cMsg_t:: +. . +typedef struct +{ + uint16_t addr; // slave address + uint16_t flags; + uint16_t len; // msg length + uint8_t *buf; // pointer to msg data +} lgI2cMsg_t; +. . + +lgLineInfo_p:: +A pointer to a lgLineInfo_t object. + +. . +typedef struct lgLine_s +{ + uint32_t offset; // GPIO number + uint32_t lFlags; + char name[LG_GPIO_NAME_LEN]; // GPIO name + char user[LG_GPIO_USER_LEN]; // user +} lgLineInfo_t, *lgLineInfo_p; +. . + +lgPulse_p:: +A pointer to a lgPulse_t object. + +. . +typedef struct lgPulse_s +{ + uint64_t bits; + uint64_t mask; + int64_t delay; +} lgPulse_t, *lgPulse_p; +. . + +lgThreadFunc_t:: +. . +typedef void *(lgThreadFunc_t) (void *); +. . + +lineInfo:: +A pointer to a lgLineInfo_t object. + +nfyHandle:: >= 0 +This associates a notification with a GPIO alert. + +*pth:: +A thread identifier, returned by [*lgGpioStartThread*]. + +pthread_t:: +A thread identifier. + +pulseCycles:: >= 0 +The number of PWM pulses to generate. A value of 0 means infinite. + +pulseOff:: >= 0 +The off period for a PWM pulse in microseconds. + +pulseOffset:: >= 0 +The offset in microseconds from the nominal PWM pulse start. + +pulseOn:: >= 0 +The on period for a PWM pulse in microseconds. + +pulses:: +An pointer to an array of lgPulse_t objects. + +pulseWidth:: 0, 500-2500 microseconds +Servo pulse width + +pwmCycles:: >= 0 +The number of PWM pulses to generate. A value of 0 means infinite. + +pwmDutyCycle:: 0-100 % +PWM duty cycle % + +pwmFrequency:: 0.1-10000 Hz +PWM frequency + +pwmOffset:: >= 0 +The offset in microseconds from the nominal PWM pulse start. + +*rxBuf:: +A pointer to a buffer used to receive data. + +rxCount:: +The size of an input buffer. + +*segs:: +An array of segments which make up a combined I2C transaction. + +serBaud:: +The speed of serial communication in bits per second. + +*serDev:: +The name of a serial tty device, e.g. /dev/ttyAMA0, /dev/ttyUSB0, /dev/tty1. + +serFlags:: +Flags which modify a serial open command. None are currently defined. + +servoCycles:: >= 0 +The number of servo pulses to generate. A value of 0 means infinite. + +servoFrequency:: 40-500 Hz +Servo pulse frequency + +servoOffset:: >= 0 +The offset in microseconds from the nominal servo pulse start. + +sleepSecs:: >= 0.0 +The number of seconds to sleep (may be fractional). + +spiBaud:: +The speed of serial communication in bits per second. + +spiChan:: +A SPI channel, >= 0. + +spiDev:: +A SPI device, >= 0. + +spiFlags:: +See [*lgSpiOpen*]. + +*txBuf:: +An pointer to a buffer of data to transmit. + +txCount:: +The size of an output buffer. + +uint64_t:: +A 64-bit unsigned value. + +*userdata:: +A pointer to arbitrary user data. This may be used to identify the instance. + +You must ensure that the pointer is in scope at the time it is processed. If +it is a pointer to a global this is automatic. Do not pass the address of a +local variable. If you want to pass a transient object then use the +following technique. + +In the calling function: + +. . +user_type *userdata; +user_type my_userdata; + +userdata = malloc(sizeof(user_type)); +*userdata = my_userdata; +. . + +In the receiving function: + +. . +user_type my_userdata = *(user_type*)userdata; + +free(userdata); +. . + +void:: +Denoting no parameter is required. + +watchdog_us:: +The watchdog time in microseconds. + +wordVal:: 0-65535 +A 16-bit value. + +PARAMS*/ + +/*DEF_S Error Codes*/ + +#define LG_OKAY 0 // No error +#define LG_INIT_FAILED -1 // initialisation failed +#define LG_BAD_MICROS -2 // micros not 0-999999 +#define LG_BAD_PATHNAME -3 // can not open pathname +#define LG_NO_HANDLE -4 // no handle available +#define LG_BAD_HANDLE -5 // unknown handle +#define LG_BAD_SOCKET_PORT -6 // socket port not 1024-32000 +#define LG_NOT_PERMITTED -7 // GPIO operation not permitted +#define LG_SOME_PERMITTED -8 // one or more GPIO not permitted +#define LG_BAD_SCRIPT -9 // invalid script +#define LG_BAD_TX_TYPE -10 // bad tx type for GPIO and group +#define LG_GPIO_IN_USE -11 // GPIO already in use +#define LG_BAD_PARAM_NUM -12 // script parameter id not 0-9 +#define LG_DUP_TAG -13 // script has duplicate tag +#define LG_TOO_MANY_TAGS -14 // script has too many tags +#define LG_BAD_SCRIPT_CMD -15 // illegal script command +#define LG_BAD_VAR_NUM -16 // script variable id not 0-149 +#define LG_NO_SCRIPT_ROOM -17 // no more room for scripts +#define LG_NO_MEMORY -18 // can not allocate temporary memory +#define LG_SOCK_READ_FAILED -19 // socket read failed +#define LG_SOCK_WRIT_FAILED -20 // socket write failed +#define LG_TOO_MANY_PARAM -21 // too many script parameters (> 10) +#define LG_SCRIPT_NOT_READY -22 // script initialising +#define LG_BAD_TAG -23 // script has unresolved tag +#define LG_BAD_MICS_DELAY -24 // bad MICS delay (too large) +#define LG_BAD_MILS_DELAY -25 // bad MILS delay (too large) +#define LG_I2C_OPEN_FAILED -26 // can not open I2C device +#define LG_SERIAL_OPEN_FAILED -27 // can not open serial device +#define LG_SPI_OPEN_FAILED -28 // can not open SPI device +#define LG_BAD_I2C_BUS -29 // bad I2C bus +#define LG_BAD_I2C_ADDR -30 // bad I2C address +#define LG_BAD_SPI_CHANNEL -31 // bad SPI channel +#define LG_BAD_I2C_FLAGS -32 // bad I2C open flags +#define LG_BAD_SPI_FLAGS -33 // bad SPI open flags +#define LG_BAD_SERIAL_FLAGS -34 // bad serial open flags +#define LG_BAD_SPI_SPEED -35 // bad SPI speed +#define LG_BAD_SERIAL_DEVICE -36 // bad serial device name +#define LG_BAD_SERIAL_SPEED -37 // bad serial baud rate +#define LG_BAD_FILE_PARAM -38 // bad file parameter +#define LG_BAD_I2C_PARAM -39 // bad I2C parameter +#define LG_BAD_SERIAL_PARAM -40 // bad serial parameter +#define LG_I2C_WRITE_FAILED -41 // i2c write failed +#define LG_I2C_READ_FAILED -42 // i2c read failed +#define LG_BAD_SPI_COUNT -43 // bad SPI count +#define LG_SERIAL_WRITE_FAILED -44 // ser write failed +#define LG_SERIAL_READ_FAILED -45 // ser read failed +#define LG_SERIAL_READ_NO_DATA -46 // ser read no data available +#define LG_UNKNOWN_COMMAND -47 // unknown command +#define LG_SPI_XFER_FAILED -48 // spi xfer/read/write failed +#define LG_BAD_POINTER -49 // bad (NULL) pointer +#define LG_MSG_TOOBIG -50 // socket/pipe message too big +#define LG_BAD_MALLOC_MODE -51 // bad memory allocation mode +#define LG_TOO_MANY_SEGS -52 // too many I2C transaction segments +#define LG_BAD_I2C_SEG -53 // an I2C transaction segment failed +#define LG_BAD_SMBUS_CMD -54 // SMBus command not supported by driver +#define LG_BAD_I2C_WLEN -55 // bad I2C write length +#define LG_BAD_I2C_RLEN -56 // bad I2C read length +#define LG_BAD_I2C_CMD -57 // bad I2C command +#define LG_FILE_OPEN_FAILED -58 // file open failed +#define LG_BAD_FILE_MODE -59 // bad file mode +#define LG_BAD_FILE_FLAG -60 // bad file flag +#define LG_BAD_FILE_READ -61 // bad file read +#define LG_BAD_FILE_WRITE -62 // bad file write +#define LG_FILE_NOT_ROPEN -63 // file not open for read +#define LG_FILE_NOT_WOPEN -64 // file not open for write +#define LG_BAD_FILE_SEEK -65 // bad file seek +#define LG_NO_FILE_MATCH -66 // no files match pattern +#define LG_NO_FILE_ACCESS -67 // no permission to access file +#define LG_FILE_IS_A_DIR -68 // file is a directory +#define LG_BAD_SHELL_STATUS -69 // bad shell return status +#define LG_BAD_SCRIPT_NAME -70 // bad script name +#define LG_CMD_INTERRUPTED -71 // Python socket command interrupted +#define LG_BAD_EVENT_REQUEST -72 // bad event request +#define LG_BAD_GPIO_NUMBER -73 // bad GPIO number +#define LG_BAD_GROUP_SIZE -74 // bad group size +#define LG_BAD_LINEINFO_IOCTL -75 // bad lineinfo IOCTL +#define LG_BAD_READ -76 // bad GPIO read +#define LG_BAD_WRITE -77 // bad GPIO write +#define LG_CANNOT_OPEN_CHIP -78 // can not open gpiochip +#define LG_GPIO_BUSY -79 // GPIO busy +#define LG_GPIO_NOT_ALLOCATED -80 // GPIO not allocated +#define LG_NOT_A_GPIOCHIP -81 // not a gpiochip +#define LG_NOT_ENOUGH_MEMORY -82 // not enough memory +#define LG_POLL_FAILED -83 // GPIO poll failed +#define LG_TOO_MANY_GPIOS -84 // too many GPIO +#define LG_UNEGPECTED_ERROR -85 // unexpected error +#define LG_BAD_PWM_MICROS -86 // bad PWM micros +#define LG_NOT_GROUP_LEADER -87 // GPIO not the group leader +#define LG_SPI_IOCTL_FAILED -88 // SPI iOCTL failed +#define LG_BAD_GPIOCHIP -89 // bad gpiochip +#define LG_BAD_CHIPINFO_IOCTL -90 // bad chipinfo IOCTL +#define LG_BAD_CONFIG_FILE -91 // bad configuration file +#define LG_BAD_CONFIG_VALUE -92 // bad configuration value +#define LG_NO_PERMISSIONS -93 // no permission to perform action +#define LG_BAD_USERNAME -94 // bad user name +#define LG_BAD_SECRET -95 // bad secret for user +#define LG_TX_QUEUE_FULL -96 // TX queue full +#define LG_BAD_CONFIG_ID -97 // bad configuration id +#define LG_BAD_DEBOUNCE_MICS -98 // bad debounce microseconds +#define LG_BAD_WATCHDOG_MICS -99 // bad watchdog microseconds +#define LG_BAD_SERVO_FREQ -100 // bad servo frequency +#define LG_BAD_SERVO_WIDTH -101 // bad servo pulsewidth +#define LG_BAD_PWM_FREQ -102 // bad PWM frequency +#define LG_BAD_PWM_DUTY -103 // bad PWM dutycycle +#define LG_GPIO_NOT_AN_OUTPUT -104 // GPIO not set as an output +#define LG_INVALID_GROUP_ALERT -105 // can not set a group to alert + +/*DEF_E*/ + + +#endif + diff --git a/rgpio.3 b/rgpio.3 new file mode 100644 index 0000000..785de0f --- /dev/null +++ b/rgpio.3 @@ -0,0 +1,5416 @@ + +." Process this file with +." groff -man -Tascii lgd_if.3 +." +.TH rgpio 3 2020-2020 Linux "lg archive" +.SH NAME +rgpio - A C library to manipulate a remote SBC's GPIO. + +.SH SYNOPSIS + +#include + + +gcc -Wall -o prog prog.c -lrgpio + + ./prog +.SH DESCRIPTION + + +.ad l + +.nh + +.br + +.br +rgpio is a C library which allows remote control of the GPIO and +other functions of Linux SBCs running the rgpiod daemon. + +.br + +.br +The rgpiod daemon must be running on the SBCs you wish to control. + +.br + +.br +.SS Features +.br + +.br + +.br +o reading and writing GPIO singly and in groups + +.br +o software timed PWM and waves + +.br +o GPIO callbacks + +.br +o pipe notification of GPIO alerts + +.br +o I2C wrapper + +.br +o SPI wrapper + +.br +o serial link wrapper + +.br +o simple file handling + +.br +o creating and running scripts on the rgpiod daemon + +.br +o a simple interface to start and stop new threads + +.br + +.br +.SS Usage +.br + +.br +Include in your source files. + +.br + +.br +Assuming your source is in prog.c use the following command to build + +.br + +.br + +.EX +gcc -Wall -o prog prog.c -lrgpio +.br + +.EE + +.br + +.br +to run make sure the rgpiod daemon is running + +.br + +.br + +.EX +rgpiod& +.br + +.br + ./prog +.br + +.EE + +.br + +.br +For examples see the lg archive file. + +.br + +.br +.SS Notes +.br + +.br +All the functions which return an int return < 0 on error + +.br + +.br + +.SH OVERVIEW + +.br +.SS ESSENTIAL +.br + +.br +rgpiod_start Connects to a rgpiod daemon +.br +rgpiod_stop Disconnects from a rgpiod daemon +.br +.SS FILES +.br + +.br +file_open Opens a file +.br +file_close Closes a file +.br + +.br +file_read Reads bytes from a file +.br +file_write Writes bytes to a file +.br + +.br +file_seek Seeks to a position within a file +.br + +.br +file_list List files which match a pattern +.br +.SS GPIO +.br + +.br +gpiochip_open Opens a gpiochip device +.br +gpiochip_close Closes a gpiochip device +.br + +.br +gpio_get_chip_info Gets gpiochip information +.br +gpio_get_line_info Gets gpiochip line information +.br +gpio_get_mode Gets the mode of a GPIO +.br + +.br +gpio_claim_input Claims a GPIO for input +.br +gpio_claim_output Claims a GPIO for output +.br +gpio_claim_alert Claims a GPIO for alerts +.br +gpio_free Frees a GPIO +.br + +.br +group_claim_input Claims a group of GPIO for inputs +.br +group_claim_output Claims a group of GPIO for outputs +.br +group_free Frees a group of GPIO +.br + +.br +gpio_read Reads a GPIO +.br +gpio_write Writes a GPIO +.br + +.br +group_read Reads a group of GPIO +.br +group_write Writes a group of GPIO +.br + +.br +tx_pulse Starts pulses on a GPIO +.br +tx_pwm Starts PWM on a GPIO +.br +tx_servo Starts servo pulses on a GPIO. +.br +tx_wave Starts a wave on a group of GPIO +.br +tx_busy See if tx is active on a GPIO or group +.br +tx_room See if more room for tx on a GPIO or group +.br + +.br +gpio_set_debounce_time Sets the debounce time for a GPIO +.br +gpio_set_watchdog_time Sets the watchdog time for a GPIO +.br + +.br +callback Starts a GPIO callback +.br +callback_cancel Stops a GPIO callback +.br +.SS I2C +.br + +.br +i2c_open Opens an I2C device +.br +i2c_close Closes an I2C device +.br + +.br +i2c_write_quick smbus write quick +.br + +.br +i2c_read_byte smbus read byte +.br +i2c_write_byte smbus write byte +.br + +.br +i2c_read_byte_data smbus read byte data +.br +i2c_write_byte_data smbus write byte data +.br + +.br +i2c_read_word_data smbus read word data +.br +i2c_write_word_data smbus write word data +.br + +.br +i2c_read_block_data smbus read block data +.br +i2c_write_block_data smbus write block data +.br + +.br +i2c_read_i2c_block_data smbus read I2C block data +.br +i2c_write_i2c_block_data smbus write I2C block data +.br + +.br +i2c_read_device Reads the raw I2C device +.br +i2c_write_device Writes the raw I2C device +.br + +.br +i2c_process_call smbus process call +.br +i2c_block_process_call smbus block process call +.br + +.br +i2c_zip Performs multiple I2C transactions +.br +.SS NOTIFICATIONS +.br + +.br +notify_open Request a notification handle +.br +notify_close Close a notification +.br +notify_pause Pause notifications +.br +notify_resume Start notifications for selected GPIO +.br +.SS SCRIPTS +.br + +.br +script_store Store a script +.br +script_run Run a stored script +.br +script_update Set a scripts parameters +.br +script_status Get script status and parameters +.br +script_stop Stop a running script +.br +script_delete Delete a stored script +.br +.SS SERIAL +.br + +.br +serial_open Opens a serial device +.br +serial_close Closes a serial device +.br + +.br +serial_read_byte Reads a byte from a serial device +.br +serial_write_byte Writes a byte to a serial device +.br + +.br +serial_read Reads bytes from a serial device +.br +serial_write Writes bytes to a serial device +.br + +.br +serial_data_available Returns number of bytes ready to be read +.br +.SS SHELL +.br + +.br +shell Executes a shell command +.br +.SS SPI +.br + +.br +spi_open Opens a SPI device +.br +spi_close Closes a SPI device +.br + +.br +spi_read Reads bytes from a SPI device +.br +spi_write Writes bytes to a SPI device +.br +spi_xfer Transfers bytes with a SPI device +.br +.SS THREADS +.br + +.br +thread_start Start a new thread +.br +thread_stop Stop a previously started thread +.br +.SS UTILITIES +.br + +.br +lgu_get_sbc_name Get the SBC name +.br + +.br +lgu_get_internal Get a SBC configuration value +.br +lgu_set_internal Set a SBC configuration value +.br + +.br +lgu_time Returns the number of seconds since the epoch +.br +lgu_timestamp Returns the number of nanoseconds since the epoch +.br + +.br +lgu_sleep Sleeps for a number of seconds +.br + +.br +lgu_set_user Set the user (and associated permissions) +.br + +.br +lgu_set_share_id Set the share id for a resource +.br +lgu_use_share_id Use this share id when asking for a resource +.br + +.br +lgu_rgpio_version Get the rgpio library version +.br +lgu_error_text Get the error text for an error code +.br +.SH FUNCTIONS + +.IP "\fBint rgpiod_start(const char *addrStr, const char *portStr)\fP" +.IP "" 4 +Connect to the rgpiod daemon. Reserving command and +notification streams. + +.br + +.br + +.EX +addrStr: specifies the host or IP address of the SBC running the +.br + rgpiod daemon. It may be NULL in which case localhost +.br + is used unless overridden by the LG_ADDR environment +.br + variable. +.br + +.br +portStr: specifies the port address used by the SBC running the +.br + rgpiod daemon. It may be NULL in which case "8889" +.br + is used unless overridden by the LG_PORT environment +.br + variable. +.br + +.EE + +.br + +.br +If OK returns a sbc (>= 0). + +.br + +.br +On failure returns a negative error code. + +.br + +.br +This sbc value is passed to the other functions to specify +the SBC to be operated on. + +.IP "\fBvoid rgpiod_stop(int sbc)\fP" +.IP "" 4 +Terminates the connection to a rgpiod daemon and frees +resources used by the library. + +.br + +.br + +.EX +sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br + +.EE + +.IP "\fBint file_open(int sbc, const char *file, int mode)\fP" +.IP "" 4 +This function returns a handle to a file opened in a specified mode. + +.br + +.br +This is a privileged command. See \fBpermits\fP. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +file: the file to open. +.br +mode: the file open mode. +.br + +.EE + +.br + +.br +If OK returns a handle (>= 0). + +.br + +.br +On failure returns a negative error code. + +.br + +.br +File + +.br + +.br +A file may only be opened if permission is granted by an entry in +the [files] section of the permits file. This is intended to allow +remote access to files in a controlled manner. + +.br + +.br +Mode + +.br + +.br +The mode may have the following values. + +.br + +.br +Macro Value Meaning +.br +LG_FILE_READ 1 open file for reading +.br +LG_FILE_WRITE 2 open file for writing +.br +LG_FILE_RW 3 open file for reading and writing +.br + +.br + +.br +The following values may be or'd into the mode. + +.br + +.br +Macro Value Meaning +.br +LG_FILE_APPEND 4 Writes append data to the end of the file +.br +LG_FILE_CREATE 8 The file is created if it doesn't exist +.br +LG_FILE_TRUNC 16 The file is truncated +.br + +.br + +.br +Newly created files are owned by the user who launched the daemon +with permissions owner read and write. + +.br + +.br +\fBExample\fP +.br + +.EX +#include +.br +#include +.br + +.br +int main(int argc, char *argv[]) +.br +{ +.br + int sbc, handle, c; +.br + char buf[60000]; +.br + +.br + sbc = rgpiod_start(NULL, NULL); +.br + +.br + if (sbc < 0) return 1; +.br + +.br + handle = file_open(sbc, "/ram/lg.c", LG_FILE_READ); +.br + +.br + if (handle >= 0) +.br + { +.br + while ((c=file_read(sbc, handle, buf, sizeof(buf)-1))) +.br + { +.br + buf[c] = 0; +.br + printf("%s", buf); +.br + } +.br + +.br + file_close(sbc, handle); +.br + } +.br + +.br + rgpiod_stop(sbc); +.br +} +.br + +.EE + +.IP "\fBint file_close(int sbc, int handle)\fP" +.IP "" 4 +This function closes the file. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBfile_open\fP). +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +\fBExample\fP +.br + +.EX +file_close(sbc, handle); +.br + +.EE + +.IP "\fBint file_write(int sbc, int handle, const char *buf, int count)\fP" +.IP "" 4 +This function writes count bytes from buf to the the file. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBfile_open\fP). +.br + buf: the array of bytes to write. +.br + count: the number of bytes to write. +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +\fBExample\fP +.br + +.EX +if (file_write(sbc, handle, buf, 100) == 0) +.br +{ +.br + // file written okay +.br +} +.br +else +.br +{ +.br + // error +.br +} +.br + +.EE + +.IP "\fBint file_read(int sbc, int handle, char *buf, int count)\fP" +.IP "" 4 +This function reads up to count bytes from the the file. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBfile_open\fP). +.br + buf: an array to receive the read data. +.br + count: the maximum number of bytes to read. +.br + +.EE + +.br + +.br +If OK returns the count of bytes read and updates buf. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +\fBExample\fP +.br + +.EX + bytes = file_read(sbc, handle, buf, sizeof(buf)); +.br + +.br + if (bytes >= 0) +.br + { +.br + // process read data +.br + } +.br + +.EE + +.IP "\fBint file_seek(int sbc, int handle, int32_t seekOffset, int seekFrom)\fP" +.IP "" 4 +This function seeks to a position within the file. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br + handle: >= 0 (as returned by \fBfile_open\fP). +.br +seekOffset: the number of bytes to move. Positive offsets +.br + move forward, negative offsets backwards. +.br + seekFrom: one of LG_FROM_START (0), LG_FROM_CURRENT (1), +.br + or LG_FROM_END (2). +.br + +.EE + +.br + +.br +If OK returns the new file position. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +\fBExample\fP +.br + +.EX +file_seek(sbc, handle, 123, LG_FROM_START); // Start plus 123 +.br + +.br +size = file_seek(sbc, handle, 0, LG_FROM_END); // End, return size +.br + +.br +pos = file_seek(sbc, handle, 0, LG_FROM_CURRENT); // Current position +.br + +.EE + +.IP "\fBint file_list(int sbc, const char *fpat, char *buf, int count)\fP" +.IP "" 4 +This function returns a list of files which match a pattern. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br + fpat: file pattern to match. +.br + buf: an array to receive the matching file names. +.br +count: the maximum number of bytes to read. +.br + +.EE + +.br + +.br +If OK returns the count of bytes read and updates buf with +the matching filenames (the filenames are separated by newline +characters). + +.br + +.br +On failure returns a negative error code. + +.br + +.br +\fBExample\fP +.br + +.EX +#include +.br +#include +.br + +.br +int main(int argc, char *argv[]) +.br +{ +.br + int sbc, handle, c; +.br + char buf[60000]; +.br + +.br + sbc = rgpiod_start(NULL, NULL); +.br + +.br + if (sbc < 0) return 1; +.br + +.br + c = file_list(sbc, "/ram/p*.c", buf, sizeof(buf)); +.br + +.br + if (c >= 0) +.br + { +.br + buf[c] = 0; +.br + printf("%s", buf); +.br + } +.br + +.br + rgpiod_stop(sbc); +.br +} +.br + +.EE + +.IP "\fBint gpiochip_open(int sbc, int gpioDev)\fP" +.IP "" 4 +This returns a handle to a gpiochip device. + +.br + +.br +This is a privileged command. See \fBpermits\fP. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +gpioDev: >= 0 +.br + +.EE + +.br + +.br +If OK returns a handle (>= 0). + +.br + +.br +On failure returns a negative error code. + +.br + +.br +\fBExample\fP +.br + +.EX +h = gpiochip_open(sbc, 0); // open gpiochip0 +.br + +.br +if (h >= 0) +.br +{ +.br + // open ok +.br +} +.br +else +.br +{ +.br + // open error +.br +} +.br + +.EE + +.br + +.br + +.IP "\fBint gpiochip_close(int sbc, int handle)\fP" +.IP "" 4 +This closes a gpiochip device. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBgpiochip_open\fP). +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +\fBExample\fP +.br + +.EX +status = gpiochip_close(sbc, h); // close gpiochip +.br + +.br +if (status < 0) +.br +{ +.br + // close failed +.br +} +.br + +.EE + +.IP "\fBint gpio_get_chip_info(int sbc, int handle, lgChipInfo_p chipInfo)\fP" +.IP "" 4 +This returns summary information of an opened gpiochip. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br + handle: >= 0 (as returned by \fBgpiochip_open\fP). +.br +chipInfo: address to store returned chip info. +.br + +.EE + +.br + +.br +If OK returns a list of okay status, number of +lines, name, and label. + +.br + +.br +On failure returns a negative error code. + +.IP "\fBint gpio_get_line_info(int sbc, int handle, int gpio, lgLineInfo_p lineInfo)\fP" +.IP "" 4 +This returns detailed information of a GPIO of +an opened gpiochip. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br + handle: >= 0 (as returned by \fBgpiochip_open\fP). +.br + gpio: the GPIO. +.br +lineInfo: address to store returned line info. +.br + +.EE + +.br + +.br +If OK returns a list of okay status, offset, +line flags, name, and user. + +.br + +.br +On failure returns a negative error code. + +.IP "\fBint gpio_get_mode(int sbc, int handle, int gpio)\fP" +.IP "" 4 +This returns the mode of a GPIO. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBgpiochip_open\fP). +.br + gpio: the GPIO to be read. +.br + +.EE + +.br + +.br +If OK returns the mode of the GPIO. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +Mode bit Value Meaning +.br +0 1 Kernel: In use by the kernel +.br +1 2 Kernel: Output +.br +2 4 Kernel: Active low +.br +3 8 Kernel: Open drain +.br +4 16 Kernel: Open source +.br +5 32 Kernel: --- +.br +6 64 Kernel: --- +.br +7 128 Kernel: --- +.br +8 256 LG: Input +.br +9 512 LG: Output +.br +10 1024 LG: Alert +.br +11 2048 LG: Group +.br +12 4096 LG: --- +.br +13 8192 LG: --- +.br +14 16384 LG: --- +.br +15 32768 LG: --- +.br + +.IP "\fBint gpio_claim_input(int sbc, int handle, int lFlags, int gpio)\fP" +.IP "" 4 +This claims a GPIO for input. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBgpiochip_open\fP). +.br +lFlags: line flags for the GPIO. +.br + gpio: the GPIO to be claimed. +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +The line flags may be used to set the GPIO +as active low, open drain, or open source. + +.br + +.br +\fBExample\fP +.br + +.EX +status = gpio_claim_input(sbc, h, 0, 23); // open GPIO 23 for input +.br + +.EE + +.IP "\fBint gpio_claim_output(int sbc, int handle, int lFlags, int gpio, int value)\fP" +.IP "" 4 +This claims a GPIO for output. + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBgpiochip_open\fP). +.br +lFlags: line flags for the GPIO. +.br + gpio: the GPIO to be claimed. +.br + value: the initial value for the GPIO. +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +The line flags may be used to set the GPIO +as active low, open drain, or open source. + +.br + +.br +If value is zero the GPIO will be initialised low (0). If any other +value is used the GPIO will be initialised high (1). + +.br + +.br +\fBExample\fP +.br + +.EX +status = gpio_claim_output(sbc, h, 0, 35, 1); // open GPIO 35 for high output +.br + +.EE + +.IP "\fBint gpio_free(int sbc, int handle, int gpio)\fP" +.IP "" 4 +This frees a GPIO. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBgpiochip_open\fP). +.br + gpio: the GPIO to be freed. +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +The GPIO may now be claimed by another user or for a different purpose. + +.IP "\fBint group_claim_input(int sbc, int handle, int lFlags, int count, const int *gpios)\fP" +.IP "" 4 +This claims a group of GPIO for inputs. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBgpiochip_open\fP). +.br +lFlags: line flags for each GPIO. +.br + count: the number of GPIO to claim. +.br + gpios: the group GPIO. +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +The line flags may be used to set the group as active low, +open drain, or open source. + +.br + +.br +gpios is an array of one or more GPIO. The first GPIO in the array +is called the group leader and is used to reference the group as a whole. + +.IP "\fBint group_claim_output(int sbc, int handle, int lFlags, int count, const int *gpios, const int *values)\fP" +.IP "" 4 +This claims a group of GPIO to be used as outputs. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBgpiochip_open\fP). +.br +lFlags: line flags for each GPIO. +.br + count: the number of GPIO to claim. +.br + gpios: the group GPIO. +.br +values: the initial value for each GPIO. +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +The line flags may be used to set the group as active low, open drain, or open source. + +.br + +.br +gpios is an array of one or more GPIO. The first GPIO in the array is +called the group leader and is used to reference the group as a whole. + +.br + +.br +values 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). + +.IP "\fBint group_free(int sbc, int handle, int gpio)\fP" +.IP "" 4 +This frees all the group GPIO. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBgpiochip_open\fP). +.br + gpio: the group leader. +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +The GPIO may now be claimed by another user or for a different purpose. + +.IP "\fBint gpio_read(int sbc, int handle, int gpio)\fP" +.IP "" 4 +This returns the level of a GPIO. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBgpiochip_open\fP). +.br + gpio: the GPIO to be read. +.br + +.EE + +.br + +.br +If OK returns 0 (low) or 1 (high). + +.br + +.br +On failure returns a negative error code. + +.br + +.br +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. + +.IP "\fBint gpio_write(int sbc, int handle, int gpio, int value)\fP" +.IP "" 4 +This sets the level of an output GPIO. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBgpiochip_open\fP). +.br + gpio: the GPIO to be written. +.br + value: the value to write. +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +This command will work for any GPIO claimed as an output (even if +a member of a group). + +.br + +.br +If level is zero the GPIO will be set low (0). If any other value +is used the GPIO will be set high (1). + +.IP "\fBint group_read(int sbc, int handle, int gpio, uint64_t *groupBits)\fP" +.IP "" 4 +This returns the levels read from a group. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br + handle: >= 0 (as returned by \fBgpiochip_open\fP). +.br + gpio: the offset of a member of the GPIO group to be read. +.br +groupBits: a pointer to a 64-bit memory area for the returned value. +.br + +.EE + +.br + +.br +If OK returns the group size and updates groupBits. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +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. + +.br + +.br +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. + +.br + +.br +After a successful read groupBits is set as follows. + +.br + +.br +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. + +.IP "\fBint group_write(int sbc, int handle, int gpio, uint64_t groupBits, uint64_t groupMask)\fP" +.IP "" 4 +This sets the levels of an output output group. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br + handle: >= 0 (as returned by \fBgpiochip_open\fP). +.br + gpio: the offset of a member of the GPIO group to be written. +.br +groupBits: the level to set if the corresponding bit in groupMask is set. +.br +groupMask: a mask indicating the group GPIO to be updated. +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +The values of each GPIO of the group are set according to the bits +of group_bits. + +.br + +.br +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. + +.br + +.br +However this may be overridden by the group_mask. A GPIO is only +updated if the corresponding bit in the mask is 1. + +.IP "\fBint tx_pulse(int sbc, int handle, int gpio, int pulse_on, int pulse_off, int pulse_offset, int pulse_cycles)\fP" +.IP "" 4 +This starts software timed pulses on an output GPIO. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br + handle: >= 0 (as returned by \fBgpiochip_open\fP). +.br + gpio: GPIO to be written. +.br + pulse_on: pulse high time in microseconds. +.br + pulse_off: pulse low time in microseconds. +.br +pulse_offset: offset from nominal pulse start position. +.br +pulse_cycles: the number of pulses to be sent, 0 for infinite. +.br + +.EE + +.br + +.br +If OK returns the number of entries left in the PWM queue for the GPIO. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +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. + +.br + +.br +Each successful call to this function consumes one PWM queue entry. + +.br + +.br +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. + +.br + +.br +PWM is characterised by two values, its frequency +(number of cycles per second) and its duty cycle +(percentage of high time per cycle). + +.br + +.br +The set frequency will be 1000000 / (pulse_on + pulse_off) Hz. + +.br + +.br +The set duty cycle will be pulse_on / (pulse_on + pulse_off) * 100 %. + +.br + +.br +E.g. if pulse_on is 50 and pulse_off is 100 the frequency will +be 6666.67 Hz and the duty cycle will be 33.33 %. + +.br + +.br +pulse_offset is a microsecond offset from the natural start +of the pulse cycle. + +.br + +.br +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. + +.br + +.br +Another pulse command may be issued to the GPIO before the last +has finished. + +.br + +.br +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. + +.br + +.br +Multiple pulse settings may be queued in this way. + +.IP "\fBint tx_pwm(int sbc, int handle, int gpio, float pwmFrequency, float pwmDutyCycle, int pwmOffset, int pwmCycles)\fP" +.IP "" 4 +This starts software timed PWM on an output GPIO. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br + handle: >= 0 (as returned by \fBgpiochip_open\fP) +.br + gpio: the GPIO to be pulsed +.br +pwmFrequency: PWM frequency in Hz (0=off, 0.1-10000) +.br +pwmDutyCycle: PWM duty cycle in % (0-100) +.br + pwmOffset: offset from nominal pulse start position +.br + pwmCycles: the number of pulses to be sent, 0 for infinite +.br + +.EE + +.br + +.br +If OK returns the number of entries left in the PWM queue for the GPIO. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +Each successful call to this function consumes one PWM queue entry. + +.br + +.br +PWM is characterised by two values, its frequency (number of cycles +per second) and its duty cycle (percentage of high time per cycle). + +.br + +.br +Another PWM command may be issued to the GPIO before the last has finished. + +.br + +.br +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. + +.br + +.br +Multiple PWM settings may be queued in this way. + +.IP "\fBint tx_servo(int sbc, int handle, int gpio, int pulseWidth, int servoFrequency, int servoOffset, int servoCycles)\fP" +.IP "" 4 +This starts software timed servo pulses on an output GPIO. + +.br + +.br +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. + +.br + +.br + +.EX + handle: >= 0 (as returned by \fBgpiochip_open\fP) +.br + gpio: the GPIO to be pulsed +.br + pulseWidth: pulse high time in microseconds (0=0ff, 500-2500) +.br +servoFrequency: the number of pulses per second (40-500) +.br + servoOffset: offset from nominal pulse start position +.br + servoCycles: the number of pulses to be sent, 0 for infinite +.br + +.EE + +.br + +.br +If OK returns the number of entries left in the PWM queue for the GPIO. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +Each successful call to this function consumes one PWM queue entry. + +.br + +.br +Another servo command may be issued to the GPIO before the last +has finished. + +.br + +.br +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. + +.br + +.br +Multiple servo settings may be queued in this way. + +.IP "\fBint tx_wave(int sbc, int handle, int gpio, int count, lgPulse_p pulses)\fP" +.IP "" 4 +This starts a software timed wave on an output group. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBgpiochip_open\fP). +.br + gpio: group leader. +.br + count: the number of pulses in the wave. +.br +pulses: the pulses. +.br + +.EE + +.br + +.br +If OK returns the number of entries left in the wave queue for the group. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +Each successful call to this function consumes one wave queue entry. + +.br + +.br +This command starts a wave of pulses. + +.br + +.br +pulses is an array of pulses to be transmitted on the group. + +.br + +.br +Each pulse is defined by the following triplet: + +.br + +.br +bits: the levels to set for the selected GPIO +.br +mask: the GPIO to select +.br +delay: the delay in microseconds before the next pulse + +.br + +.br +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. + +.br + +.br +Multiple waves may be queued in this way. + +.IP "\fBint tx_busy(int sbc, int handle, int gpio, int kind)\fP" +.IP "" 4 +This returns true if transmissions of the specified kind +are active on the GPIO or group. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBgpiochip_open\fP). +.br + gpio: the GPIO or group to be tested. +.br + kind: LG_TX_PWM or LG_TX_WAVE. +.br + +.EE + +.br + +.br +If OK returns 1 for busy and 0 for not busy. + +.br + +.br +On failure returns a negative error code. + +.br + +.br + +.IP "\fBint tx_room(int sbc, int handle, int gpio, int kind)\fP" +.IP "" 4 +This returns the number of entries there are to queue further +transmissions of the specified kind on a GPIO or GPIO group. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBgpiochip_open\fP). +.br + gpio: the GPIO or group to be tested. +.br + kind: LG_TX_PWM or LG_TX_WAVE. +.br + +.EE + +.br + +.br +If OK returns the number of free entries (0 for none). + +.br + +.br +On failure returns a negative error code. + +.br + +.br + +.IP "\fBint gpio_set_debounce_time(int sbc, int handle, int gpio, int debounce_us)\fP" +.IP "" 4 +This sets the debounce time for a GPIO. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br + handle: >= 0 (as returned by \fBgpiochip_open\fP). +.br + gpio: the GPIO to be configured. +.br +debounce_us: the debounce time in microseconds. +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +This only affects alerts. + +.br + +.br +An alert will only be issued if the edge has been stable for at +least debounce microseconds. + +.br + +.br +Generally this is used to debounce mechanical switches +(e.g. contact bounce). + +.br + +.br +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. + +.br + +.br +Note that level changes will be timestamped debounce microseconds +after the actual level change. + +.IP "\fBint gpio_set_watchdog_time(int sbc, int handle, int gpio, int watchdog_us)\fP" +.IP "" 4 +This sets the watchdog time for a GPIO. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br + handle: >= 0 (as returned by \fBgpiochip_open\fP). +.br + gpio: the GPIO to be configured. +.br +watchdog_us: the watchdog time in microseconds. +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +This only affects alerts. + +.br + +.br +A watchdog alert will be sent if no edge alert has been issued +for that GPIO in the previous watchdog microseconds. + +.br + +.br +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. + +.br + +.br +The level is set to LG_TIMEOUT (2) for a watchdog alert. + +.IP "\fBint gpio_claim_alert(int sbc, int handle, int lFlags, int eFlags, int gpio, int nfyHandle)\fP" +.IP "" 4 +This claims a GPIO to be used as a source of alerts on level changes. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br + handle: >= 0 (as returned by \fBgpiochip_open\fP). +.br + gpio: >= 0, as legal for the gpiochip. +.br +.br +.br + lFlags: line flags for the GPIO. +.br + eFlags: event flags for the GPIO. +.br +nfyHandle: >=0, a notification handle (use -1 for callbacks). +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br + +.br + +.br +The line flags may be used to set the GPIO as active low, +open drain, or open source. + +.br + +.br +The event flags are used to generate alerts for a rising edge, +falling edge, or both edges. + +.br + +.br +Use a notification handle of -1 unless you plan to read the alerts +from a notification pipe you have opened. + +.IP "\fBint callback(int sbc, int handle, int gpio, int edge, CBFunc_t f, void *userdata)\fP" +.IP "" 4 +This function initialises a new callback. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br + handle: >= 0,(as returned by \fBgpiochip_open\fP). +.br + gpio: >= 0, as legal for the gpiochip. +.br + edge: RISING_EDGE, FALLING_EDGE, or BOTH_EDGES. +.br + f: the callback function. +.br +userdata: a pointer to arbitrary user data. +.br + +.EE + +.br + +.br +If OK returns a callback id. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +The user supplied callback receives the chip, GPIO, edge, timestamp, +and the userdata pointer, whenever the GPIO has the identified edge. + +.br + +.br +The reported level will be one of + +.br + +.br +0: change to low (a falling edge) +1: change to high (a rising edge) +2: no level change (a watchdog timeout) + +.br + +.br +The timestamp is when the change happened reported as the +number of nanoseconds since the epoch (start of 1970). + +.br + +.br +If you want to track the level of more than one GPIO do so by +maintaining the state in the callback. Do not use \fBgpio_read\fP. +Remember the alert that triggered the callback may have +happened several milliseconds before and the GPIO may have +changed level many times since then. + +.IP "\fBint callback_cancel(int callback_id)\fP" +.IP "" 4 +This function cancels a callback identified by its id. + +.br + +.br + +.EX +callback_id: >= 0 (as returned by \fBcallback\fP). +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br + +.IP "\fBint i2c_open(int sbc, int i2c_bus, int i2c_addr, int i2c_flags)\fP" +.IP "" 4 +This returns a handle for the device at address i2c_addr on bus i2c_bus. + +.br + +.br +This is a privileged command. See \fBpermits\fP. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br + i2c_bus: >= 0. +.br + i2c_addr: 0-0x7F. +.br +i2c_flags: 0. +.br + +.EE + +.br + +.br +If OK returns a handle (>= 0). + +.br + +.br +On failure returns a negative error code. + +.br + +.br +No flags are currently defined. This parameter should be set to zero. + +.br + +.br +For the SMBus commands the low level transactions are shown at the end +of the function description. The following abbreviations are used. + +.br + +.br + +.EX +S (1 bit) : Start bit +.br +P (1 bit) : Stop bit +.br +Rd/Wr (1 bit) : Read/Write bit. Rd equals 1, Wr equals 0. +.br +A, NA (1 bit) : Accept and not accept bit. +.br +.br +.br +Addr (7 bits): I2C 7 bit address. +.br +i2c_reg (8 bits): A byte which often selects a register. +.br +Data (8 bits): A data byte. +.br +Count (8 bits): A byte defining the length of a block operation. +.br + +.br +[..]: Data sent by the device. +.br + +.EE + +.IP "\fBint i2c_close(int sbc, int handle)\fP" +.IP "" 4 +This closes the I2C device + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBi2c_open\fP). +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.IP "\fBint i2c_write_quick(int sbc, int handle, int bitVal)\fP" +.IP "" 4 +This sends a single bit (in the Rd/Wr bit) to the device. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBi2c_open\fP). +.br +bitVal: 0-1, the value to write. +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +Quick command. SMBus 2.0 5.5.1 + +.EX +S Addr bit [A] P +.br + +.EE + +.IP "\fBint i2c_write_byte(int sbc, int handle, int byteVal)\fP" +.IP "" 4 +This sends a single byte to the device. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br + handle: >= 0 (as returned by \fBi2c_open\fP). +.br +byteVal: 0-0xFF, the value to write. +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +Send byte. SMBus 2.0 5.5.2 + +.EX +S Addr Wr [A] byteVal [A] P +.br + +.EE + +.IP "\fBint i2c_read_byte(int sbc, int handle)\fP" +.IP "" 4 +This reads a single byte from the device. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBi2c_open\fP). +.br + +.EE + +.br + +.br +If OK returns the byte read (0-255). + +.br + +.br +On failure returns a negative error code. + +.br + +.br +Receive byte. SMBus 2.0 5.5.3 + +.EX +S Addr Rd [A] [Data] NA P +.br + +.EE + +.IP "\fBint i2c_write_byte_data(int sbc, int handle, int i2c_reg, int byteVal)\fP" +.IP "" 4 +This writes a single byte to the specified register of the device. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br + handle: >= 0 (as returned by \fBi2c_open\fP). +.br +i2c_reg: 0-255, the register to write. +.br +byteVal: 0-0xFF, the value to write. +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +Write byte. SMBus 2.0 5.5.4 + +.EX +S Addr Wr [A] i2c_reg [A] byteVal [A] P +.br + +.EE + +.IP "\fBint i2c_write_word_data(int sbc, int handle, int i2c_reg, int wordVal)\fP" +.IP "" 4 +This writes a single 16 bit word to the specified register of the device. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br + handle: >= 0 (as returned by \fBi2c_open\fP). +.br +i2c_reg: 0-255, the register to write. +.br +wordVal: 0-0xFFFF, the value to write. +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +Write word. SMBus 2.0 5.5.4 + +.EX +S Addr Wr [A] i2c_reg [A] wval_Low [A] wVal_High [A] P +.br + +.EE + +.IP "\fBint i2c_read_byte_data(int sbc, int handle, int i2c_reg)\fP" +.IP "" 4 +This reads a single byte from the specified register of the device. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br + handle: >= 0 (as returned by \fBi2c_open\fP). +.br +i2c_reg: 0-255, the register to read. +.br + +.EE + +.br + +.br +If OK returns the read byte (0-255). + +.br + +.br +On failure returns a negative error code. + +.br + +.br +Read byte. SMBus 2.0 5.5.5 + +.EX +S Addr Wr [A] i2c_reg [A] S Addr Rd [A] [Data] NA P +.br + +.EE + +.IP "\fBint i2c_read_word_data(int sbc, int handle, int i2c_reg)\fP" +.IP "" 4 +This reads a single 16 bit word from the specified register of the device. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br + handle: >= 0 (as returned by \fBi2c_open\fP). +.br +i2c_reg: 0-255, the register to read. +.br + +.EE + +.br + +.br +If OK returns the read word (0-65535). + +.br + +.br +On failure returns a negative error code. + +.br + +.br +Read word. SMBus 2.0 5.5.5 + +.EX +S Addr Wr [A] i2c_reg [A] +.br + S Addr Rd [A] [DataLow] A [DataHigh] NA P +.br + +.EE + +.IP "\fBint i2c_process_call(int sbc, int handle, int i2c_reg, int wordVal)\fP" +.IP "" 4 +This writes 16 bits of data to the specified register of the device +and reads 16 bits of data in return. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br + handle: >= 0 (as returned by \fBi2c_open\fP). +.br +i2c_reg: 0-255, the register to write/read. +.br +wordVal: 0-0xFFFF, the value to write. +.br + +.EE + +.br + +.br +If OK returns the read word (0-65535). + +.br + +.br +On failure returns a negative error code. + +.br + +.br +Process call. SMBus 2.0 5.5.6 + +.EX +S Addr Wr [A] i2c_reg [A] wVal_Low [A] wVal_High [A] +.br + S Addr Rd [A] [DataLow] A [DataHigh] NA P +.br + +.EE + +.IP "\fBint i2c_write_block_data(int sbc, int handle, int i2c_reg, const char *buf, int count)\fP" +.IP "" 4 +This writes up to 32 bytes to the specified register of the device. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br + handle: >= 0 (as returned by \fBi2c_open\fP). +.br +i2c_reg: 0-255, the register to write. +.br + buf: an array with the data to send. +.br + count: 1-32, the number of bytes to write. +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +Block write. SMBus 2.0 5.5.7 + +.EX +S Addr Wr [A] i2c_reg [A] count [A] buf0 [A] buf1 [A] ... +.br + [A] bufn [A] P +.br + +.EE + +.IP "\fBint i2c_read_block_data(int sbc, int handle, int i2c_reg, char *buf)\fP" +.IP "" 4 +This reads a block of up to 32 bytes from the specified register of +the device. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br + handle: >= 0 (as returned by \fBi2c_open\fP). +.br +i2c_reg: 0-255, the register to read. +.br + buf: an array to receive the read data. +.br + +.EE + +.br + +.br +If OK returns the count of bytes read and updates buf. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +The amount of returned data is set by the device. + +.br + +.br +Block read. SMBus 2.0 5.5.7 + +.EX +S Addr Wr [A] i2c_reg [A] +.br + S Addr Rd [A] [Count] A [buf0] A [buf1] A ... A [bufn] NA P +.br + +.EE + +.IP "\fBint i2c_block_process_call(int sbc, int handle, int i2c_reg, char *buf, int count)\fP" +.IP "" 4 +This writes data bytes to the specified register of the device +and reads a device specified number of bytes of data in return. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br + handle: >= 0 (as returned by \fBi2c_open\fP). +.br +i2c_reg: 0-255, the register to write/read. +.br + buf: an array with the data to send and to receive the read data. +.br + count: 1-32, the number of bytes to write. +.br + +.EE + +.br + +.br +If OK returns the count of bytes read and updates buf. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +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. + +.br + +.br +Block write-block read. SMBus 2.0 5.5.8 + +.EX +S Addr Wr [A] i2c_reg [A] count [A] buf0 [A] ... +.br + S Addr Rd [A] [Count] A [Data] ... A P +.br + +.EE + +.IP "\fBint i2c_read_i2c_block_data(int sbc, int handle, int i2c_reg, char *buf, int count)\fP" +.IP "" 4 +This reads count bytes from the specified register of the device. +The count may be 1-32. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br + handle: >= 0 (as returned by \fBi2c_open\fP). +.br +i2c_reg: 0-255, the register to read. +.br + buf: an array to receive the read data. +.br + count: 1-32, the number of bytes to read. +.br + +.EE + +.br + +.br +If OK returns the count of bytes read and updates buf. + +.br + +.br +On failure returns a negative error code. + +.br + +.br + +.EX +S Addr Wr [A] i2c_reg [A] +.br + S Addr Rd [A] [buf0] A [buf1] A ... A [bufn] NA P +.br + +.EE + +.IP "\fBint i2c_write_i2c_block_data(int sbc, int handle, int i2c_reg, const char *buf, int count)\fP" +.IP "" 4 +This writes 1 to 32 bytes to the specified register of the device. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br + handle: >= 0 (as returned by \fBi2c_open\fP). +.br +i2c_reg: 0-255, the register to write. +.br + buf: the data to write. +.br + count: 1-32, the number of bytes to write. +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br + +.EX +S Addr Wr [A] i2c_reg [A] buf0 [A] buf1 [A] ... [A] bufn [A] P +.br + +.EE + +.IP "\fBint i2c_read_device(int sbc, int handle, char *buf, int count)\fP" +.IP "" 4 +This reads count bytes from the raw device into buf. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBi2c_open\fP). +.br + buf: an array to receive the read data bytes. +.br + count: >0, the number of bytes to read. +.br + +.EE + +.br + +.br +If OK returns the count of bytes read and updates buf. + +.br + +.br +On failure returns a negative error code. + +.br + +.br + +.EX +S Addr Rd [A] [buf0] A [buf1] A ... A [bufn] NA P +.br + +.EE + +.IP "\fBint i2c_write_device(int sbc, int handle, const char *buf, int count)\fP" +.IP "" 4 +This writes count bytes from buf to the raw device. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBi2c_open\fP). +.br + buf: an array containing the data bytes to write. +.br + count: >0, the number of bytes to write. +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br + +.EX +S Addr Wr [A] buf0 [A] buf1 [A] ... [A] bufn [A] P +.br + +.EE + +.IP "\fBint i2c_zip(int sbc, int handle, const char *inBuf, int inCount, char *outBuf, int outCount)\fP" +.IP "" 4 +This function executes a sequence of I2C operations. The +operations to be performed are specified by the contents of inBuf +which contains the concatenated command codes and associated data. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br + handle: >= 0, as returned by a call to \fBlgI2cOpen\fP +.br + inBuf: pointer to the concatenated I2C commands, see below +.br + inCount: size of command buffer +.br + outBuf: pointer to buffer to hold returned data +.br +outCount: size of output buffer +.br + +.EE + +.br + +.br +If OK returns the count of bytes read and updates outBuf. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +The following command codes are supported: + +.br + +.br +Name Cmd & Data Meaning +.br +End 0 No more commands +.br +Escape 1 Next P is two bytes +.br +On 2 Switch combined flag on +.br +Off 3 Switch combined flag off +.br +Address 4 P Set I2C address to P +.br +Flags 5 lsb msb Set I2C flags to lsb + (msb << 8) +.br +Read 6 P Read P bytes of data +.br +Write 7 P ... Write P bytes of data +.br + +.br + +.br +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). + +.br + +.br +The address defaults to that associated with the handle. +The flags default to 0. The address and flags maintain their +previous value until updated. + +.br + +.br +The returned I2C data is stored in consecutive locations of outBuf. + +.br + +.br +\fBExample\fP +.br + +.EX +Set address 0x53, write 0x32, read 6 bytes +.br +Set address 0x1E, write 0x03, read 6 bytes +.br +Set address 0x68, write 0x1B, read 8 bytes +.br +End +.br + +.br +0x04 0x53 0x07 0x01 0x32 0x06 0x06 +.br +0x04 0x1E 0x07 0x01 0x03 0x06 0x06 +.br +0x04 0x68 0x07 0x01 0x1B 0x06 0x08 +.br +0x00 +.br + +.EE + +.br + +.br + +.IP "\fBint notify_open(int sbc)\fP" +.IP "" 4 +Get a free notification handle. + +.br + +.br +This is a privileged command. See \fBpermits\fP. + +.br + +.br + +.EX +sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br + +.EE + +.br + +.br +If OK returns a handle (>= 0). + +.br + +.br +On failure returns a negative error code. + +.br + +.br +A notification is a method for being notified of GPIO state +changes via a pipe. + +.br + +.br +Pipes are only accessible from the local machine so this function +serves no purpose if you are using the library from a remote machine. +The in-built (socket) notifications provided by \fBcallback\fP +should be used instead. + +.br + +.br +The notification pipes are created in the library working directory. + +.br + +.br +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. + +.br + +.br +Each notification occupies 16 bytes in the fifo and has the +following structure. + +.br + +.br + +.EX +typedef struct +.br +{ +.br + uint64_t timestamp; // alert time in nanoseconds +.br + uint8_t chip; // gpiochip device number +.br + uint8_t gpio; // offset into gpio device +.br + uint8_t level; // 0=low, 1=high, 2=timeout +.br + uint8_t flags; // none currently defined +.br +} lgGpioReport_t; +.br + +.EE + +.br + +.br +timestamp: the number of nanoseconds since the epoch (start of 1970) +chip: the gpiochip device number (NOT the handle). +.br +gpio: the GPIO. +.br +level: indicates the level of the GPIO +.br +flags: no flags are currently defined + +.br + +.br +For future proofing it is probably best to ignore any notification +with non-zero flags. + +.br + +.br + +.IP "\fBint notify_resume(int sbc, int handle)\fP" +.IP "" 4 +Resume notifications on a handle. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBnotify_open\fP) +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.IP "\fBint notify_pause(int sbc, int handle)\fP" +.IP "" 4 +Pauses notifications on a handle. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBnotify_open\fP) +.br + +.EE + +.br + +.br + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +Notifications for the handle are suspended until +\fBnotify_resume\fP is called. + +.IP "\fBint notify_close(int sbc, int handle)\fP" +.IP "" 4 +Stop notifications and releases the handle. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBnotify_open\fP) +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.IP "\fBint script_store(int sbc, const char *script)\fP" +.IP "" 4 +This function stores a script for later execution. + +.br + +.br +This is a privileged command. See \fBpermits\fP. + +.br + +.br +See \fBscripts.html\fP for details. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +script: the text of the script. +.br + +.EE + +.br + +.br +If OK returns a handle (>=0). + +.br + +.br +On failure returns a negative error code. + +.IP "\fBint script_run(int sbc, int handle, int count, const uint32_t *param)\fP" +.IP "" 4 +This function runs a stored script. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBscript_store\fP). +.br + count: 0-10, the number of parameters. +.br + param: an array of parameters. +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +param is an array of up to 10 parameters which may be referenced in +the script as p0 to p9. + +.IP "\fBint script_update(int sbc, int handle, int count, const uint32_t *param)\fP" +.IP "" 4 +This function sets the parameters of a script. The script may or +may not be running. The first numPar parameters of the script are +overwritten with the new values. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBscript_store\fP). +.br + count: 0-10, the number of parameters. +.br + param: an array of parameters. +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +param is an array of up to 10 parameters which may be referenced in +the script as p0 to p9. + +.IP "\fBint script_status(int sbc, int handle, uint32_t *param)\fP" +.IP "" 4 +This function returns the run status of a stored script as well +as the current values of parameters 0 to 9. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBscript_store\fP). +.br + param: an array to hold the returned 10 parameters. +.br + +.EE + +.br + +.br +If OK returns the script status and updates param. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +The script status may be + +.br + +.br + +.EX +LG_SCRIPT_INITING +.br +LG_SCRIPT_READY +.br +LG_SCRIPT_RUNNING +.br +LG_SCRIPT_WAITING +.br +LG_SCRIPT_ENDED +.br +LG_SCRIPT_HALTED +.br +LG_SCRIPT_FAILED +.br + +.EE + +.br + +.br +The current value of script parameters 0 to 9 are returned in param. + +.IP "\fBint script_stop(int sbc, int handle)\fP" +.IP "" 4 +This function stops a running script. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBscript_store\fP). +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.IP "\fBint script_delete(int sbc, int handle)\fP" +.IP "" 4 +This function deletes a stored script. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBscript_store\fP). +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.IP "\fBint serial_open(int sbc, const char *ser_tty, int ser_baud, int ser_flags)\fP" +.IP "" 4 +This function opens a serial device at a specified baud rate +with specified flags. The device must be present in /dev. + +.br + +.br +This is a privileged command. See \fBpermits\fP. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br + ser_tty: the serial device to open. +.br + ser_baud: the baud rate in bits per second, see below. +.br +ser_flags: 0. +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +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. + +.br + +.br +No flags are currently defined. This parameter should be set to zero. + +.IP "\fBint serial_close(int sbc, int handle)\fP" +.IP "" 4 +This function closes the serial device. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBserial_open\fP). +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.IP "\fBint serial_write_byte(int sbc, int handle, int byteVal)\fP" +.IP "" 4 +This function writes byteVal to the serial port. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBserial_open\fP). +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.IP "\fBint serial_read_byte(int sbc, int handle)\fP" +.IP "" 4 +This function reads a byte from the serial port. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBserial_open\fP). +.br + +.EE + +.br + +.br +If OK returns the read byte (0-255). + +.br + +.br +On failure returns a negative error code. + +.IP "\fBint serial_write(int sbc, int handle, const char *buf, int count)\fP" +.IP "" 4 +This function writes count bytes from buf to the the serial port. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBserial_open\fP). +.br + buf: the array of bytes to write. +.br + count: the number of bytes to write. +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.IP "\fBint serial_read(int sbc, int handle, char *buf, int count)\fP" +.IP "" 4 +This function reads up to count bytes from the the serial port +and writes them to buf. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBserial_open\fP). +.br + buf: an array to receive the read data. +.br + count: the maximum number of bytes to read. +.br + +.EE + +.br + +.br +If OK returns the count of bytes read and updates buf. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +If no data is ready zero is returned. + +.IP "\fBint serial_data_available(int sbc, int handle)\fP" +.IP "" 4 +Returns the number of bytes available to be read from the +device. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBserial_open\fP). +.br + +.EE + +.br + +.br +If OK returns the count of bytes available. + +.br + +.br +On failure returns a negative error code. + +.IP "\fBint shell(int sbc, const char *scriptName, const char *scriptString)\fP" +.IP "" 4 +This function uses the system call to execute a shell script +with the given string as its parameter. + +.br + +.br +This is a privileged command. See \fBpermits\fP. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br + scriptName: the name of the script, only alphanumeric characters, +.br + '-' and '_' are allowed in the name. +.br +scriptString: the string to pass to the script. +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +scriptName must exist in a directory named cgi in the daemon's +configuration directory and must be executable. + +.br + +.br +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. + +.br + +.br +The following table gives some example returned statuses. + +.br + +.br +Script exit status Returned system call status +.br +1 256 +.br +5 1280 +.br +10 2560 +.br +200 51200 +.br +script not found 32512 +.br + +.br + +.br +\fBExample\fP +.br + +.EX +// pass two parameters, hello and world +.br +status = shell_(sbc, "scr1", "hello world"); +.br + +.br +// pass three parameters, hello, string with spaces, and world +.br +status = shell_(sbc, "scr1", "hello 'string with spaces' world"); +.br + +.br +// pass one parameter, hello string with spaces world +.br +status = shell_(sbc, "scr1", "\"hello string with spaces world\""); +.br + +.EE + +.IP "\fBint spi_open(int sbc, int spi_device, int spi_channel, int spi_baud, int spi_flags)\fP" +.IP "" 4 +This function 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. + +.br + +.br +This is a privileged command. See \fBpermits\fP. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br + spi_device: >= 0 +.br +spi_channel: >= 0 +.br + spi_baud: SPI speed in bits per second. +.br + spi_flags: see below. +.br + +.EE + +.br + +.br +If OK returns a handle (>= 0). + +.br + +.br +On failure returns a negative error code. + +.br + +.br +spi_flags consists of the least significant 2 bits. + +.br + +.br + +.EX +1 0 +.br +m m +.br + +.EE + +.br + +.br +mm defines the SPI mode. + +.br + +.br + +.EX +Mode POL PHA +.br + 0 0 0 +.br + 1 0 1 +.br + 2 1 0 +.br + 3 1 1 +.br + +.EE + +.br + +.br + +.br + +.br +The other bits in flags should be set to zero. + +.IP "\fBint spi_close(int sbc, int handle)\fP" +.IP "" 4 +This functions closes the SPI device identified by the handle. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBspi_open\fP). +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.IP "\fBint spi_read(int sbc, int handle, char *buf, int count)\fP" +.IP "" 4 +This function reads count bytes of data from the SPI +device + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBspi_open\fP). +.br + buf: an array to receive the read data bytes. +.br + count: the number of bytes to read. +.br + +.EE + +.br + +.br +If OK returns the count of bytes read and updates buf. + +.br + +.br +On failure returns a negative error code. + +.IP "\fBint spi_write(int sbc, int handle, const char *buf, int count)\fP" +.IP "" 4 +This function writes count bytes of data from buf to the SPI +device + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBspi_open\fP). +.br + buf: the data bytes to write. +.br + count: the number of bytes to write. +.br + +.EE + +.br + +.br +If OK returns the count of bytes written. + +.br + +.br +On failure returns a negative error code. + +.IP "\fBint spi_xfer(int sbc, int handle, const char *txBuf, char *rxBuf, int count)\fP" +.IP "" 4 +This function transfers count bytes of data from txBuf to the SPI +device Simultaneously count bytes of +data are read from the device and placed in rxBuf. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +handle: >= 0 (as returned by \fBspi_open\fP). +.br + txBuf: the data bytes to write. +.br + rxBuf: the received data bytes. +.br + count: the number of bytes to transfer. +.br + +.EE + +.br + +.br +If OK returns the count of bytes transferred and updates rxBuf. + +.br + +.br +On failure returns a negative error code. + +.IP "\fBpthread_t *thread_start(lgThreadFunc_t thread_func, void *userdata)\fP" +.IP "" 4 +Starts a new thread of execution with thread_func as the main routine. + +.br + +.br + +.EX +thread_func: the main function for the new thread. +.br + userdata: a pointer to an arbitrary argument. +.br + +.EE + +.br + +.br +If OK returns a pointer to a pthread_t. + +.br + +.br +On failure returns NULL. + +.br + +.br +The function is passed the single argument userdata. + +.br + +.br +The thread can be cancelled by passing the pointer to pthread_t to +\fBthread_stop\fP. + +.IP "\fBvoid thread_stop(pthread_t *pth)\fP" +.IP "" 4 +Cancels the thread pointed at by pth. + +.br + +.br + +.EX +pth: the thread to be stopped. +.br + +.EE + +.br + +.br +No value is returned. + +.br + +.br +The thread to be stopped should have been started with \fBthread_start\fP. + +.IP "\fBint lgu_get_internal(int sbc, int config_id, uint64_t *config_value)\fP" +.IP "" 4 +Returns the value of a sbc configuration item. + +.br + +.br +This is a privileged command. See \fBpermits\fP. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br + config_id: the configuration item. +.br +config_value: pointer for returned value. +.br + +.EE + +.br + +.br +If OK returns 0 and updates config_value. + +.br + +.br +On failure returns a negative error code. + +.IP "\fBint lgu_set_internal(int sbc, int config_id, uint64_t config_value)\fP" +.IP "" 4 +Sets the value of a sbc configuration item. + +.br + +.br +This is a privileged command. See \fBpermits\fP. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br + config_id: the configuration item. +.br +config_value: the value to set. +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.IP "\fBint lgu_get_sbc_name(int sbc, char *buf, int count)\fP" +.IP "" 4 +Return the lgd server name. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br + buf: the server name is copied to this buffer. +.br +count: the maximum number of characters to copy. +.br + +.EE + +.br + +.br +If OK returns the count of bytes copied and updates buf. + +.br + +.br +On failure returns a negative error code. + +.IP "\fBint lgu_set_user(int sbc, char *user, char *secretsFile)\fP" +.IP "" 4 +Sets the rgpiod daemon user. The user then has the +associated permissions. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br + user: the user to set ("" defaults to the default user). +.br +secretsFile: the path to the shared secret file ("" defaults +.br + to "~/.lg_secret"). +.br + +.EE + +.br + +.br +If OK returns 1 if the user was set, 0 otherwise. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +\fBExample\fP +.br + +.EX +if (lgu_set_user(sbc, "gpio", "") +.br +{ +.br + printf("using user gpio permissions"); +.br +} +.br +else +.br +{ +.br + printf("using default permissions"); +.br +} +.br + +.EE + +.IP "\fBint lgu_set_share_id(int sbc, int handle, int share_id)\fP" +.IP "" 4 +Sets the share id of an owned object. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br + handle: >= 0 +.br +share_id: >= 0, 0 stops sharing. +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +Normally objects associated with a handle are only accessible +to the program which created them (and are automatically +deleted when the program ends). + +.br + +.br +If a non-zero share is set the object is accessible to any +software which knows the share and the handle (and are not +automatically deleted when the program ends). + +.br + +.br +\fBExample\fP +.br + +.EX +lgu_set_share_id(sbc, handle, 23); +.br + +.EE + +.IP "\fBint lgu_use_share_id(int sbc, int share_id)\fP" +.IP "" 4 +Sets the share id to be used when asking to use an object +owned by another creator. + +.br + +.br + +.EX + sbc: >= 0 (as returned by \fBrgpiod_start\fP). +.br +share_id: >= 0, 0 stops sharing. +.br + +.EE + +.br + +.br +If OK returns 0. + +.br + +.br +On failure returns a negative error code. + +.br + +.br +Normally objects associated with a handle are only accessible +to the program which created them (and are automatically +deleted when the program ends). + +.br + +.br +If a non-zero share is set the object is accessible to any +software which knows the share and the handle. + +.br + +.br +\fBExample\fP +.br + +.EX +lgu_use_share_id(sbc, 23); +.br + +.EE + +.IP "\fBuint32_t lgu_rgpio_version(void)\fP" +.IP "" 4 +Return the rgpio version. + +.br + +.br +If OK returns the rgpio version. + +.br + +.br +On failure returns a negative error code. + +.IP "\fBconst char *lgu_error(int errnum)\fP" +.IP "" 4 +Return a text description for an error code. + +.br + +.br + +.EX +errnum: the error code. +.br + +.EE + +.IP "\fBvoid lgu_sleep(double sleepSecs)\fP" +.IP "" 4 +Delay execution for a given number of seconds. + +.br + +.br + +.EX +sleepSecs: the number of seconds to delay. +.br + +.EE + +.IP "\fBdouble lgu_time(void)\fP" +.IP "" 4 +Return the current time in seconds since the Epoch. + +.IP "\fBuint64_t lgu_timestamp(void)\fP" +.IP "" 4 +Return the current time in nanoseconds since the Epoch. +.SH PARAMETERS + +.br + +.br + +.IP "\fB*addrStr\fP" 0 +A string specifying the host or IP address of the SBC running +the rgpiod daemon. It may be NULL in which case localhost +is used unless overridden by the LG_ADDR environment +variable. + +.br + +.br + +.IP "\fBbitVal\fP" 0 +A value of 0 or 1. + +.br + +.br + +.IP "\fB*buf\fP" 0 +A buffer to hold data being sent or being received. + +.br + +.br + +.IP "\fBbyteVal\fP: 0-255" 0 +An 8-bit byte value. + +.br + +.br + +.IP "\fBcallback_id\fP" 0 +A value >= 0, as returned by a call to the \fBcallback\fP. + +.br + +.br +The id is passed to \fBcallback_cancel\fP to cancel the callback. + +.br + +.br + +.IP "\fBCBFunc_t\fP" 0 + +.EX +typedef void (*CBFunc_t) +.br + (int sbc, int chip, int gpio, int level, uint64_t timestamp, void * userdata); +.br + +.EE + +.br + +.br + +.IP "\fBchar\fP" 0 +A single character, an 8 bit quantity able to store 0-255. + +.br + +.br + +.IP "\fBchipInfo\fP" 0 +A pointer to a lgChipInfo_t object. + +.br + +.br + +.IP "\fBconfig_id\fP" 0 +A number identifying a configuration item. + +.br + +.br + +.EX +LG_CFG_ID_DEBUG_LEVEL 0 +.br +LG_CFG_ID_MIN_DELAY 1 +.br + +.EE + +.br + +.br + +.IP "\fBconfig_value\fP" 0 +The value of a configuration item. + +.br + +.br + +.IP "\fB*config_value\fP" 0 +The value of a configuration item. + +.br + +.br + +.IP "\fBcount\fP" 0 +The number of bytes to be transferred in a file, I2C, SPI, or serial +command. + +.br + +.br + +.IP "\fBdebounce_us\fP" 0 +The debounce time in microseconds. + +.br + +.br + +.IP "\fBdouble\fP" 0 +A floating point number. + +.br + +.br + +.IP "\fBedge\fP" 0 +Used to identify a GPIO level transition of interest. A rising edge is +a level change from 0 to 1. A falling edge is a level change from 1 to 0. + +.br + +.br + +.EX +RISING_EDGE 1 +.br +FALLING_EDGE 2 +.br +BOTH_EDGES 3 +.br + +.EE + +.br + +.br + +.IP "\fBeFlags\fP" 0 +The type of GPIO edge to generate an alert. See \fBgpio_claim_alert\fP. + +.br + +.br + +.EX +RISING_EDGE 1 +.br +FALLING_EDGE 2 +.br +BOTH_EDGES 3 +.br + +.EE + +.br + +.br + +.br + +.br + +.IP "\fBerrnum\fP" 0 +A negative number indicating a function call failed and the nature +of the error. + +.br + +.br + +.IP "\fBf\fP" 0 +A function. + +.br + +.br + +.IP "\fB*file\fP" 0 +A full file path. To be accessible the path must match an entry in +the [files] section of the permits file. + +.br + +.br + +.IP "\fBfloat\fP" 0 +A floating point number. + +.br + +.br + +.IP "\fB*fpat\fP" 0 +A file path which may contain wildcards. To be accessible the path +must match an entry in the [files] section of the permits file. + +.br + +.br + +.IP "\fBgpio\fP" 0 +A 0 based offset of a GPIO within a gpiochip. + +.br + +.br + +.IP "\fBgpioDev\fP: >= 0" 0 +The device number of a gpiochip. + +.br + +.br + +.IP "\fB*gpios\fP" 0 +An array of GPIO numbers. + +.br + +.br + +.IP "\fBgroupBits\fP" 0 +A 64-bit value used to set the levels of a GPIO group. + +.br + +.br +Set bit x to set GPIO x of the group high. + +.br + +.br +Clear bit x to set GPIO x of the group low. + +.br + +.br + +.IP "\fB*groupBits\fP" 0 +A 64-bit value denoting the levels of a GPIO group. + +.br + +.br +If bit x is set then GPIO x of the group is high. + +.br + +.br + +.IP "\fBgroupMask\fP" 0 +A 64-bit value used to determine which members of a GPIO group +should be updated. + +.br + +.br +Set bit x to update GPIO x of the group. + +.br + +.br +Clear bit x to leave GPIO x of the group unaltered. + +.br + +.br + +.IP "\fBhandle\fP: >= 0" 0 +A number referencing an object opened by one of + +.br + +.br + +.br + +.br +\fBfile_open\fP +.br +\fBgpiochip_open\fP +.br +\fBi2c_open\fP +.br +\fBnotify_open\fP +.br +\fBserial_open\fP +\fBscript_store\fP +.br +\fBspi_open\fP + +.br + +.br + +.IP "\fBi2c_addr\fP: 0-0x7F" 0 +The address of a device on the I2C bus. + +.br + +.br + +.IP "\fBi2c_bus\fP: >= 0" 0 +An I2C bus number. + +.br + +.br + +.IP "\fBi2c_flags\fP: 0" 0 +Flags which modify an I2C open command. None are currently defined. + +.br + +.br + +.IP "\fBi2c_reg\fP: 0-255" 0 +A register of an I2C device. + +.br + +.br + +.IP "\fB*inBuf\fP" 0 +A buffer used to pass data to a function. + +.br + +.br + +.IP "\fBinCount\fP" 0 +The size of an input buffer. + +.br + +.br + +.IP "\fBint\fP" 0 +A whole number, negative or positive. + +.br + +.br + +.IP "\fBint32_t\fP" 0 +A 32-bit signed value. + +.br + +.br + +.IP "\fBkind\fP: LG_TX_PWM or LG_TX_WAVE" 0 +A type of transmission: PWM or wave. + +.br + +.br + +.IP "\fBlFlags\fP" 0 +Line flags for the GPIO. + +.br + +.br +The following values may be or'd to form the value. + +.br + +.br + +.EX +LG_SET_ACTIVE_LOW +.br +LG_SET_OPEN_DRAIN +.br +LG_SET_OPEN_SOURCE +.br + +.EE + +.br + +.br + +.IP "\fBlgChipInfo_p\fP" 0 +A pointer to a lgChipInfo_t object. + +.br + +.br + +.EX +typedef struct lgChipInfo_s +.br +{ +.br + uint32_t lines; // number of GPIO +.br + char name[LG_GPIO_NAME_LEN]; // Linux name +.br + char label[LG_GPIO_LABEL_LEN]; // functional name +.br +} lgChipInfo_t, *lgChipInfo_p; +.br + +.EE + +.br + +.br + +.IP "\fBlgLineInfo_p\fP" 0 +A pointer to a lgLineInfo_t object. + +.br + +.br + +.EX +typedef struct lgLine_s +.br +{ +.br + uint32_t offset; // GPIO number +.br + uint32_t lFlags; +.br + char name[LG_GPIO_NAME_LEN]; // GPIO name +.br + char user[LG_GPIO_USER_LEN]; // user +.br +} lgLineInfo_t, *lgLineInfo_p; +.br + +.EE + +.br + +.br + +.br + +.br + +.IP "\fBlgPulse_p\fP" 0 +A pointer to a lgPulse_t object. + +.br + +.br + +.EX +typedef struct lgPulse_s +.br +{ +.br + uint64_t bits; +.br + uint64_t mask; +.br + int64_t delay; +.br +} lgPulse_t, *lgPulse_p; +.br + +.EE + +.br + +.br + +.IP "\fBlgThreadFunc_t\fP" 0 + +.EX +typedef void *(lgThreadFunc_t) (void *); +.br + +.EE + +.br + +.br + +.IP "\fBlineInfo\fP" 0 +A pointer to a lgLineInfo_t object. + +.br + +.br + +.IP "\fBmode\fP" 0 +A file open mode. + +.br + +.br + +.EX +LG_FILE_READ 1 +.br +LG_FILE_WRITE 2 +.br +LG_FILE_RW 3 +.br + +.EE + +.br + +.br +The following values can be or'd into the mode. + +.br + +.br + +.EX +LG_FILE_APPEND 4 +.br +LG_FILE_CREATE 8 +.br +LG_FILE_TRUNC 16 +.br + +.EE + +.br + +.br + +.IP "\fBnfyHandle\fP: >= 0" 0 +This associates a notification with a GPIO alert. + +.br + +.br + +.IP "\fB*outBuf\fP" 0 +A buffer used to return data from a function. + +.br + +.br + +.IP "\fBoutCount\fP" 0 +The size of an output buffer. + +.br + +.br + +.IP "\fB*param\fP" 0 +An array of script parameters. + +.br + +.br + +.IP "\fB*portStr\fP" 0 +A string specifying the port address used by the SBC running +the rgpiod daemon. It may be NULL in which case "8889" +is used unless overridden by the LG_PORT environment +variable. + +.br + +.br + +.IP "\fB*pth\fP" 0 +A thread identifier, returned by \fBthread_start\fP. + +.br + +.br + +.IP "\fBpthread_t\fP" 0 +A thread identifier. + +.br + +.br + +.IP "\fBpulse_cycles\fP: >= 0" 0 +The number of pulses to generate. A value of 0 means infinite.# + +.br + +.br + +.IP "\fBpulse_off\fP: >= 0" 0 +The off period for a pulse in microseconds. + +.br + +.br + +.IP "\fBpulse_offset\fP: >= 0" 0 +The offset in microseconds from the nominal pulse start. + +.br + +.br + +.IP "\fBpulse_on\fP: >= 0" 0 +The on period for a pulse in microseconds. + +.br + +.br + +.IP "\fBpulses\fP" 0 +An pointer to an array of lgPulse_t objects. + +.br + +.br + +.IP "\fBpulseWidth\fP: 0, 500-2500 microseconds" 0 +Servo pulse width + +.br + +.br + +.IP "\fBpwmCycles\fP: >= 0" 0 +The number of PWM pulses to generate. A value of 0 means infinite. + +.br + +.br + +.IP "\fBpwmDutyCycle\fP: 0-100 %" 0 +PWM duty cycle % + +.br + +.br + +.IP "\fBpwmFrequency\fP: 0.1-10000 Hz" 0 +PWM frequency + +.br + +.br + +.IP "\fBpwmOffset\fP: >= 0" 0 +The offset in microseconds from the nominal PWM pulse start. + +.br + +.br + +.IP "\fB*rxBuf\fP" 0 +A pointer to a buffer to receive data. + +.br + +.br + +.IP "\fBsbc\fP" 0 +An integer defining a connected SBC. The value is returned by +\fBrgpiod_start\fP upon success. + +.br + +.br + +.IP "\fB*script\fP" 0 +A pointer to the text of a script. + +.br + +.br + +.IP "\fB*scriptName\fP" 0 +The name of a \fBshell_\fP script to be executed. The script must +be present in the cgi directory of the daemon's configuration +directory and must have execute permission. + +.br + +.br + +.IP "\fB*scriptString\fP" 0 +The string to be passed to a \fBshell_\fP script to be executed. + +.br + +.br + +.IP "\fB*secretsFile\fP" 0 +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. + +.br + +.br + +.IP "\fBseekFrom\fP" 0 + +.EX +LG_FROM_START 0 +.br +LG_FROM_CURRENT 1 +.br +LG_FROM_END 2 +.br + +.EE + +.br + +.br + +.IP "\fBseekOffset\fP" 0 +The number of bytes to move forward (positive) or backwards (negative) +from the seek position (start, current, or end of file). + +.br + +.br + +.IP "\fBser_baud\fP" 0 +The speed of serial communication in bits per second. + +.br + +.br + +.IP "\fBser_flags\fP" 0 +Flags which modify a serial open command. None are currently defined. + +.br + +.br + +.IP "\fB*ser_tty\fP" 0 +The name of a serial tty device, e.g. /dev/ttyAMA0, /dev/ttyUSB0, /dev/tty1. + +.br + +.br + +.IP "\fBservoCycles\fP: >= 0" 0 +The number of servo pulses to generate. A value of 0 means infinite. + +.br + +.br + +.IP "\fBservoFrequency\fP: 40-500 Hz" 0 +Servo pulse frequency + +.br + +.br + +.IP "\fBservoOffset\fP: >= 0" 0 +The offset in microseconds from the nominal servo pulse start. + +.br + +.br + +.IP "\fBshare_id\fP" 0 +Objects created with a non-zero share_id are persistent and may be +used by other software which knows the share_id. + +.br + +.br + +.IP "\fBsleepSecs\fP" 0 +The number of seconds to delay. + +.br + +.br + +.IP "\fBspi_baud\fP" 0 +The speed of SPI communication in bits per second. + +.br + +.br + +.IP "\fBspi_channel\fP: >= 0" 0 +A SPI channel. + +.br + +.br + +.IP "\fBspi_device\fP: >= 0" 0 +A SPI device. + +.br + +.br + +.IP "\fBspi_flags\fP" 0 +See \fBspi_open\fP and \fBbb_spi_open\fP. + +.br + +.br + +.IP "\fBthread_func\fP" 0 +A function of type gpioThreadFunc_t used as the main function of a +thread. + +.br + +.br + +.IP "\fB*txBuf\fP" 0 +An array of bytes to transmit. + +.br + +.br + +.IP "\fBuint32_t\fP" 0 +A 32-bit unsigned value. + +.br + +.br + +.IP "\fBuint64_t\fP" 0 +A 64-bit unsigned value. + +.br + +.br + +.IP "\fB*user\fP" 0 +A name known by the rgpiod daemon and associated with a set of user +permissions. + +.br + +.br + +.IP "\fB*userdata\fP" 0 +A pointer to arbitrary user data. This may be used to identify the instance. + +.br + +.br +You must ensure that the pointer is in scope at the time it is processed. If +it is a pointer to a global this is automatic. Do not pass the address of a +local variable. If you want to pass a transient object then use the +following technique. + +.br + +.br +In the calling function: + +.br + +.br + +.EX +user_type *userdata; +.br +.br +.br +user_type my_userdata; +.br + +.br +userdata = malloc(sizeof(user_type)); +.br +.br +.br +*userdata = my_userdata; +.br + +.EE + +.br + +.br +In the receiving function: + +.br + +.br + +.EX +user_type my_userdata = *(user_type*)userdata; +.br + +.br +free(userdata); +.br + +.EE + +.br + +.br + +.IP "\fBvalue\fP: 0-1" 0 +A GPIO level. + +.br + +.br + +.IP "\fB*values\fP" 0 +An array of GPIO values. + +.br + +.br + +.IP "\fBvoid\fP" 0 +Denoting no parameter is required + +.br + +.br + +.IP "\fBwatchdog_us\fP" 0 +The watchdog time in microseconds. + +.br + +.br + +.IP "\fBwordVal\fP: 0-65535" 0 +A 16-bit word value. + +.br + +.br +.SH rgpio Error Codes + +.EX + +.br +typedef enum +.br +{ +.br + lgif_bad_send = -2000, +.br + lgif_bad_recv = -2001, +.br + lgif_bad_getaddrinfo = -2002, +.br + lgif_bad_connect = -2003, +.br + lgif_bad_socket = -2004, +.br + lgif_bad_noib = -2005, +.br + lgif_duplicate_callback = -2006, +.br + lgif_bad_malloc = -2007, +.br + lgif_bad_callback = -2008, +.br + lgif_notify_failed = -2009, +.br + lgif_callback_not_found = -2010, +.br + lgif_unconnected_sbc = -2011, +.br + lgif_too_many_pis = -2012, +.br +} lgifError_t; +.br + +.br + +.EE + +.SH SEE ALSO + +rgpiod(1), rgs(1), lgpio(3) diff --git a/rgpio.c b/rgpio.c new file mode 100644 index 0000000..dd19b43 --- /dev/null +++ b/rgpio.c @@ -0,0 +1,1776 @@ +/* +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 +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "rgpiod.h" + +#include "rgpio.h" +#include "lgCfg.h" +#include "lgMD5.h" + +#define LG_MAX_REPORTS_PER_READ 4096 + +#define STACK_SIZE (256*1024) + +#define MAX_SBC 32 + +typedef void (*CBF_t) (); + +struct callback_s +{ + + int id; + int sbc; + int chip; + int gpio; + int edge; + CBF_t f; + void * user; + callback_t *prev; + callback_t *next; +}; + +typedef struct +{ + size_t count; // number of elements + size_t bytes; // bytes per element + size_t size; // total number of bytes + const void *ptr; +} lgExtent_t; + +/* GLOBALS ---------------------------------------------------------------- */ + +static int gAbort = 0; + +static int gPiInUse [MAX_SBC]; + +static int gPigCommand [MAX_SBC]; +static int gPigHandle [MAX_SBC]; +static int gPigNotify [MAX_SBC]; + +static uint32_t gLastLevel [MAX_SBC]; + +static pthread_t *gPthNotify [MAX_SBC]; + +static pthread_mutex_t gCmdMutex [MAX_SBC]; +static int gCancelState [MAX_SBC]; + +static uint8_t *gMsgBuf [MAX_SBC]; + +static callback_t *gCallBackFirst = 0; +static callback_t *gCallBackLast = 0; + +/* PRIVATE ---------------------------------------------------------------- */ + +static uint64_t xMakeSalt(void) +{ + struct timespec xts; + + clock_gettime(CLOCK_REALTIME, &xts); + + return ((xts.tv_sec + xts.tv_nsec) * random()) + (random() + xts.tv_nsec); +} + +static void xStopAll(void) +{ + static int xInited = 0; + + if (!xInited) gAbort = 1; + + fflush(NULL); + + xInited = 1; + + signal(SIGINT, SIG_DFL); + raise(SIGINT); +} + +static void xSignalHandler(int signum) +{ + xStopAll(); +} + +static void _pml(int sbc) +{ + int cancelState; + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cancelState); + pthread_mutex_lock(&gCmdMutex[sbc]); + gCancelState[sbc] = cancelState; +} + +static void _pmu(int sbc) +{ + int cancelState; + + cancelState = gCancelState[sbc]; + pthread_mutex_unlock(&gCmdMutex[sbc]); + pthread_setcancelstate(cancelState, NULL); +} + +static int lg_command + (int sbc, int command, int extents, lgExtent_t *ext, int rl) +{ + int i; + lgCmd_p h; + uint8_t *p; + size_t len; + + if ((sbc < 0) || (sbc >= MAX_SBC) || !gPiInUse[sbc]) + { + return lgif_unconnected_sbc; + } + + if (gAbort) + { + rgpiod_stop(sbc); + return lgif_unconnected_sbc; + } + + _pml(sbc); + + p = gMsgBuf[sbc]; + + h = (lgCmd_p) p; + + h->magic = LG_MAGIC; + h->size = 0; + h->cmd = command; + h->doubles = 0; + h->longs = 0; + h->shorts = 0; + + p += sizeof(lgCmd_t); + + for (i=0; isize += ext[i].size; + memcpy(p, ext[i].ptr, ext[i].size); + p += ext[i].size; + + switch(ext[i].bytes) + { + case 8: + h->doubles += ext[i].count; + break; + + case 4: + h->longs += ext[i].count; + break; + + case 2: + h->shorts += ext[i].count; + break; + } + } + + len = sizeof(lgCmd_t) + h->size; + + if (send(gPigCommand[sbc], gMsgBuf[sbc], len, 0) != len) + { + _pmu(sbc); + return lgif_bad_send; + } + + if (recv(gPigCommand[sbc], h, sizeof(lgCmd_t), MSG_WAITALL) != + sizeof(lgCmd_t)) + { + _pmu(sbc); + return lgif_bad_recv; + } + + if (rl) _pmu(sbc); + + return h->status; +} + + +static int lg_notify(int sbc) +{ + lgCmd_t h; + + if ((sbc < 0) || (sbc >= MAX_SBC) || !gPiInUse[sbc]) + return lgif_unconnected_sbc; + + h.magic = LG_MAGIC; + h.size = 0; + h.cmd = LG_CMD_NOIB; + h.doubles = 0; + h.longs = 0; + h.shorts = 0; + + _pml(sbc); + + if (send(gPigNotify[sbc], &h, sizeof(h), 0) != sizeof(h)) + { + _pmu(sbc); + return lgif_bad_send; + } + + if (recv(gPigNotify[sbc], &h, sizeof(h), MSG_WAITALL) != sizeof(h)) + { + _pmu(sbc); + return lgif_bad_recv; + } + + _pmu(sbc); + + return h.status; +} + +static int lg_command_0(int sbc, int command, int rl) + {return lg_command(sbc, command, 0, NULL, rl);} + +static int lg_command_1(int sbc, int command, int p1, int rl) +{ + lgExtent_t ext[1]; + uint32_t pars[]={p1}; + + ext[0].size = sizeof(pars); + ext[0].count = sizeof(pars)/sizeof(pars[0]); + ext[0].bytes = sizeof(pars[0]); + ext[0].ptr = &pars; + + return lg_command(sbc, command, 1, ext, rl); +} + +static int lg_command_2(int sbc, int command, int p1, int p2, int rl) +{ + lgExtent_t ext[1]; + uint32_t pars[]={p1, p2}; + + ext[0].size = sizeof(pars); + ext[0].count = sizeof(pars)/sizeof(pars[0]); + ext[0].bytes = sizeof(pars[0]); + ext[0].ptr = &pars; + + return lg_command(sbc, command, 1, ext, rl); +} + +static int lg_command_3(int sbc, int command, int p1, int p2, int p3, int rl) +{ + lgExtent_t ext[1]; + uint32_t pars[]={p1, p2, p3}; + + ext[0].size = sizeof(pars); + ext[0].count = sizeof(pars)/sizeof(pars[0]); + ext[0].bytes = sizeof(pars[0]); + ext[0].ptr = &pars; + + return lg_command(sbc, command, 1, ext, rl); +} + + +static int lgOpenSocket(const char *addrStr, const char *portStr) +{ + int sock, err, opt; + struct addrinfo hints, *res, *rp; + + memset (&hints, 0, sizeof (hints)); + + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags |= AI_CANONNAME; + + err = getaddrinfo (addrStr, portStr, &hints, &res); + + if (err) return lgif_bad_getaddrinfo; + + for (rp=res; rp!=NULL; rp=rp->ai_next) + { + sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + + if (sock == -1) continue; + + /* Disable the Nagle algorithm. */ + opt = 1; + setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char*)&opt, sizeof(int)); + + if (connect(sock, rp->ai_addr, rp->ai_addrlen) != -1) break; + } + + freeaddrinfo(res); + + if (rp == NULL) return lgif_bad_connect; + + return sock; +} + +static void dispatch_notification(int sbc, lgGpioReport_t *r) +{ + callback_t *p; + +/* + fprintf(stderr, "ts=%"PRIu64" c=%d g=%d l=%d f=%d\n", + r->timestamp, r->chip, r->gpio, r->level, r->flags); +*/ + + if (r->flags == 0) + { + p = gCallBackFirst; + + while (p) + { + if ((p->sbc == sbc) && (p->chip == r->chip) && (p->gpio == r->gpio)) + { + (p->f)(sbc, p->chip, p->gpio, r->level, r->timestamp, p->user); + } + p = p->next; + } + } + else /* no flags currently defined, ignore */ + { + } +} + +static void *pthNotifyThread(void *x) +{ + static int got = 0; + int sbc; + int bytes, r; + lgGpioReport_t report[LG_MAX_REPORTS_PER_READ]; + + sbc = *((int*)x); + free(x); /* memory allocated in rgpiod_start */ + + while (1) + { + bytes = read(gPigNotify[sbc], (char*)&report+got, sizeof(report)-got); + + if (bytes > 0) got += bytes; + else break; + + r = 0; + + while (got >= sizeof(lgGpioReport_t)) + { + dispatch_notification(sbc, &report[r]); + + r++; + + got -= sizeof(lgGpioReport_t); + } + + /* copy any partial report to start of array */ + + if (got && r) report[0] = report[r]; + } + + fprintf(stderr, "notify thread for sbc %d broke with read error %d\n", + sbc, bytes); + + while (1) sleep(1); + + return NULL; +} + +static int intCallback( + int sbc, int chip, int gpio, int edge, void *f, void *user) +{ + static int id = 0; + callback_t *p; + + /* + printf("sbc=%d chip=%d gpio=%d edge=%d f=%p u=%p\n", + sbc, chip, gpio, edge, f, user); + */ + + if ((chip < 64) && (gpio < 64) && (edge <= 3) && f) + { + /* prevent duplicates */ + + p = gCallBackFirst; + + while (p) + { + if ((p->sbc == sbc) && + (p->chip == chip) && + (p->gpio == gpio) && + (p->edge == edge) && + (p->f == f)) + { + return lgif_duplicate_callback; + } + p = p->next; + } + + p = malloc(sizeof(callback_t)); + + if (p) + { + if (!gCallBackFirst) gCallBackFirst = p; + + p->id = id++; + p->sbc = sbc; + p->chip = chip; + p->gpio = gpio; + p->edge = edge; + p->f = f; + p->user = user; + p->next = 0; + p->prev = gCallBackLast; + + if (p->prev) (p->prev)->next = p; + gCallBackLast = p; + + return p->id; + } + + return lgif_bad_malloc; + } + + return lgif_bad_callback; +} + +static int recvMax(int sbc, void *buf, int bufsize, int sent) +{ + /* + Copy at most bufSize bytes from the receieved message to + buf. Discard the rest of the message. + */ + uint8_t scratch[4096]; + int remaining, fetch, count; + + if (sent < bufsize) count = sent; else count = bufsize; + + if (count) recv(gPigCommand[sbc], buf, count, MSG_WAITALL); + + remaining = sent - count; + + while (remaining) + { + fetch = remaining; + if (fetch > sizeof(scratch)) fetch = sizeof(scratch); + recv(gPigCommand[sbc], scratch, fetch, MSG_WAITALL); + remaining -= fetch; + } + + return count; +} + +/* PUBLIC ----------------------------------------------------------------- */ + +/* START/STOP */ + +int rgpiod_start(const char *addrStr, const char *portStr) +{ + static int xInited = 0; + int sbc; + int *userdata; + struct sigaction new_action, old_action; + + if (!xInited) + { + for (sbc=0; sbc= MAX_SBC) return lgif_too_many_pis; + + if ((!addrStr) || (!strlen(addrStr))) + { + addrStr = getenv(LG_ENVADDR); + + if ((!addrStr) || (!strlen(addrStr))) + { + addrStr = LG_DEFAULT_SOCKET_ADDR_STR; + } + } + + if ((!portStr) || (!strlen(portStr))) + { + portStr = getenv(LG_ENVPORT); + + if ((!portStr) || (!strlen(portStr))) + { + portStr = LG_DEFAULT_SOCKET_PORT_STR; + } + } + + gPigCommand[sbc] = lgOpenSocket(addrStr, portStr); + + if (gPigCommand[sbc] >= 0) + { + gPigNotify[sbc] = lgOpenSocket(addrStr, portStr); + + if (gPigNotify[sbc] >= 0) + { + gPigHandle[sbc] = lg_notify(sbc); + + if (gPigHandle[sbc] < 0) return lgif_bad_noib; + else + { + gLastLevel[sbc] = 0; + + /* must be freed by pthNotifyThread */ + userdata = malloc(sizeof(*userdata)); + *userdata = sbc; + + gPthNotify[sbc] = thread_start(pthNotifyThread, userdata); + + if (gPthNotify[sbc]) + { + gMsgBuf[sbc] = malloc(CMD_MAX_EXTENSION); + if (gMsgBuf[sbc] != NULL) return sbc; + else return lgif_bad_malloc; + } + else return lgif_notify_failed; + + } + } + else return gPigNotify[sbc]; + } + else return gPigCommand[sbc]; +} + +void rgpiod_stop(int sbc) +{ + if ((sbc < 0) || (sbc >= MAX_SBC) || !gPiInUse[sbc]) return; + + if (gPthNotify[sbc]) + { + thread_stop(gPthNotify[sbc]); + gPthNotify[sbc] = 0; + } + + if (gPigCommand[sbc] >= 0) + { + //lg_command_0(sbc, LG_CMD_FREE, 1); + + if (gPigHandle[sbc] >= 0) + { + //lg_command_1(sbc, LG_CMD_NC, gPigHandle[sbc], 1); + gPigHandle[sbc] = -1; + } + + close(gPigCommand[sbc]); + gPigCommand[sbc] = -1; + } + + if (gPigNotify[sbc] >= 0) + { + close(gPigNotify[sbc]); + gPigNotify[sbc] = -1; + } + + _pml(sbc); + gPiInUse[sbc] = 0; + free(gMsgBuf[sbc]); + gMsgBuf[sbc] = NULL; + _pmu(sbc); +} + + +/* FILES */ + +int file_open(int sbc, const char *file, int mode) +{ + lgExtent_t ext[2]; + uint32_t pars[] = {mode}; + int len; + + len = strlen(file); + + ext[0].size = sizeof(pars); + ext[0].count = sizeof(pars)/sizeof(pars[0]); + ext[0].bytes = sizeof(pars[0]); + ext[0].ptr = &pars; + + ext[1].size = len; + ext[1].count = len; + ext[1].bytes = 1; + ext[1].ptr = file; + + return lg_command(sbc, LG_CMD_FO, 2, ext, 1); +} + +int file_close(int sbc, int handle) + {return lg_command_1(sbc, LG_CMD_FC, handle, 1);} + +int file_write(int sbc, int handle, const char *buf, int count) +{ + lgExtent_t ext[2]; + uint32_t pars[]={handle}; + + ext[0].size = sizeof(pars); + ext[0].count = sizeof(pars)/sizeof(pars[0]); + ext[0].bytes = sizeof(pars[0]); + ext[0].ptr = &pars; + + ext[1].size = count; + ext[1].ptr = buf; + + return lg_command(sbc, LG_CMD_FW, 2, ext, 1); +} + +int file_read(int sbc, int handle, char *buf, int count) +{ + int bytes; + + bytes = lg_command_2(sbc, LG_CMD_FR, handle, count, 0); + + if (bytes > 0) + { + bytes = recvMax(sbc, buf, count, bytes); + } + + _pmu(sbc); + + return bytes; +} + +int file_seek(int sbc, int handle, int32_t seekOffset, int seekFrom) + {return lg_command_3(sbc, LG_CMD_FS, handle, seekOffset, seekFrom, 1);} + +int file_list(int sbc, const char *fpat, char *buf, int count) +{ + int len; + int bytes; + uint32_t pars[] = {60000}; + lgExtent_t ext[2]; + + len = strlen(fpat); + + ext[0].size = sizeof(pars); + ext[0].count = sizeof(pars)/sizeof(pars[0]); + ext[0].bytes = sizeof(pars[0]); + ext[0].ptr = &pars; + + ext[1].size = len; + ext[1].count = len; + ext[1].bytes = 1; + ext[1].ptr = fpat; + + bytes = lg_command(sbc, LG_CMD_FL, 2, ext, 0); + + if (bytes > 0) + { + bytes = recvMax(sbc, buf, count, bytes); + } + + _pmu(sbc); + + return bytes; +} + +/* GPIO */ + +int gpiochip_open(int sbc, int device) +{ + int status; + + status = lg_command_1(sbc, LG_CMD_GO, device, 1); + if (status >= 0) status |= (device<<16); + + return status; +} + + +int gpiochip_close(int sbc, int handle) + {return lg_command_1(sbc, LG_CMD_GC, handle&0xffff, 1);} + +int gpio_get_chip_info(int sbc, int handle, lgChipInfo_p chipInfP) +{ + int status; + int bytes; + lgExtent_t ext[1]; + uint32_t pars[] = {handle&0xffff}; + + ext[0].size = sizeof(pars); + ext[0].count = sizeof(pars)/sizeof(pars[0]); + ext[0].bytes = sizeof(pars[0]); + ext[0].ptr = &pars; + + bytes = lg_command(sbc, LG_CMD_GIC, 1, ext, 0); + + if (bytes > 0) + { + recvMax(sbc, chipInfP, sizeof(lgChipInfo_t), bytes); + status = LG_OKAY; + } + else status = bytes; + + _pmu(sbc); + + return status; +} + +int gpio_get_line_info(int sbc, int handle, int gpio, lgLineInfo_p lineInfP) +{ + int status; + int bytes; + lgExtent_t ext[1]; + uint32_t pars[] = {handle&0xffff, gpio}; + + ext[0].size = sizeof(pars); + ext[0].count = sizeof(pars)/sizeof(pars[0]); + ext[0].bytes = sizeof(pars[0]); + ext[0].ptr = &pars; + + bytes = lg_command(sbc, LG_CMD_GIL, 1, ext, 0); + + if (bytes > 0) + { + recvMax(sbc, lineInfP, sizeof(lgLineInfo_t), bytes); + status = LG_OKAY; + } + else status = bytes; + + _pmu(sbc); + + return status; +} + +int gpio_get_mode(int sbc, int handle, int gpio) + {return lg_command_2(sbc, LG_CMD_GMODE, handle&0xffff, gpio, 1);} + +int group_read(int sbc, int handle, int group, uint64_t *value) +{ + int status; + int bytes; + lgExtent_t ext[1]; + uint32_t pars[] = {handle&0xffff, group}; + uint8_t retval[12]; + + ext[0].size = sizeof(pars); + ext[0].count = sizeof(pars)/sizeof(pars[0]); + ext[0].bytes = sizeof(pars[0]); + ext[0].ptr = &pars; + + bytes = lg_command(sbc, LG_CMD_GGR, 1, ext, 0); + + if (bytes > 0) + { + recvMax(sbc, &retval, 12, bytes); + *value = *(uint64_t*)retval; + status = *(uint32_t*)(retval+8); + } + else status = bytes; + + _pmu(sbc); + + return status; +} + + +int group_write( + int sbc, int handle, int group, uint64_t groupBits, uint64_t groupMask) +{ + lgExtent_t ext[2]; + uint64_t parq[] = {groupBits, groupMask}; + uint32_t pars[] = {handle&0xffff, group}; + + ext[0].size = sizeof(parq); + ext[0].count = sizeof(parq)/sizeof(parq[0]); + ext[0].bytes = sizeof(parq[0]); + ext[0].ptr = &parq; + + ext[1].size = sizeof(pars); + ext[1].count = sizeof(pars)/sizeof(pars[0]); + ext[1].bytes = sizeof(pars[0]); + ext[1].ptr = &pars; + + return lg_command(sbc, LG_CMD_GGWX, 2, ext, 1); +} + +int gpio_claim_input(int sbc, int handle, int lFlags, int gpio) + {return lg_command_3(sbc, LG_CMD_GSIX, handle&0xffff, lFlags, gpio, 1);} + +int group_claim_input( + int sbc, int handle, int lFlags, int size, const int *gpio) +{ + lgExtent_t ext[2]; + uint32_t pars[] = {handle&0xffff, lFlags}; + int i; + int status=LG_NO_MEMORY; + uint32_t *g32 = malloc(size * 4); + + if (g32) + { + ext[0].size = sizeof(pars); + ext[0].count = sizeof(pars)/sizeof(pars[0]); + ext[0].bytes = sizeof(pars[0]); + ext[0].ptr = &pars; + + ext[1].size = size*4; + ext[1].count = size; + ext[1].bytes = 4; + ext[1].ptr = g32; + + for (i=0; i>16, gpio, edge, f, user); +} + +int callback_cancel(int id) +{ + callback_t *p; + + p = gCallBackFirst; + + while (p) + { + if (p->id == id) + { + if (p->prev) {p->prev->next = p->next;} + else {gCallBackFirst = p->next;} + + if (p->next) {p->next->prev = p->prev;} + else {gCallBackLast = p->prev;} + + free(p); + + return 0; + } + p = p->next; + } + return lgif_callback_not_found; +} + +/* I2C */ + +int i2c_open(int sbc, int i2c_bus, int i2c_addr, int i2c_flags) + {return lg_command_3(sbc, LG_CMD_I2CO, i2c_bus, i2c_addr, i2c_flags, 1);} + +int i2c_close(int sbc, int handle) + {return lg_command_1(sbc, LG_CMD_I2CC, handle, 1);} + +int i2c_write_quick(int sbc, int handle, int bit) + {return lg_command_2(sbc, LG_CMD_I2CWQ, handle, bit, 1);} + +int i2c_write_byte(int sbc, int handle, int val) + {return lg_command_2(sbc, LG_CMD_I2CWS, handle, val, 1);} + +int i2c_read_byte(int sbc, int handle) + {return lg_command_1(sbc, LG_CMD_I2CRS, handle, 1);} + +int i2c_write_byte_data(int sbc, int handle, int reg, int val) + {return lg_command_3(sbc, LG_CMD_I2CWB, handle, reg, val, 1);} + +int i2c_write_word_data(int sbc, int handle, int reg, int val) + {return lg_command_3(sbc, LG_CMD_I2CWW, handle, reg, val, 1);} + +int i2c_read_byte_data(int sbc, int handle, int reg) + {return lg_command_2(sbc, LG_CMD_I2CRB, handle, reg, 1);} + +int i2c_read_word_data(int sbc, int handle, int reg) + {return lg_command_2(sbc, LG_CMD_I2CRW, handle, reg, 1);} + +int i2c_process_call(int sbc, int handle, int reg, int val) + {return lg_command_3(sbc, LG_CMD_I2CPC, handle, reg, val, 1);} + +int i2c_write_block_data( + int sbc, int handle, int reg, const char *buf, int count) +{ + lgExtent_t ext[2]; + uint32_t pars[] = {handle, reg}; + + ext[0].size = sizeof(pars); + ext[0].count = sizeof(pars)/sizeof(pars[0]); + ext[0].bytes = sizeof(pars[0]); + ext[0].ptr = &pars; + + ext[1].size = count; + ext[1].count = count; + ext[1].bytes = 1; + ext[1].ptr = buf; + + return lg_command(sbc, LG_CMD_I2CWK, 2, ext, 1); +} + +int i2c_read_block_data(int sbc, int handle, int reg, char *buf) +{ + int bytes; + + bytes = lg_command_2(sbc, LG_CMD_I2CRK, handle, reg, 0); + + if (bytes > 0) + { + bytes = recvMax(sbc, buf, 32, bytes); + } + + _pmu(sbc); + + return bytes; +} + +int i2c_block_process_call( + int sbc, int handle, int reg, char *buf, int count) +{ + int bytes; + lgExtent_t ext[2]; + uint32_t pars[]= {handle, reg}; + + ext[0].size = sizeof(pars); + ext[0].count = sizeof(pars)/sizeof(pars[0]); + ext[0].bytes = sizeof(pars[0]); + ext[0].ptr = &pars; + + ext[1].size = count; + ext[1].count = count; + ext[1].bytes = 1; + ext[1].ptr = buf; + + bytes = lg_command(sbc, LG_CMD_I2CPK, 2, ext, 0); + + if (bytes > 0) + { + bytes = recvMax(sbc, buf, 32, bytes); + } + + _pmu(sbc); + + return bytes; +} + +int i2c_read_i2c_block_data( + int sbc, int handle, int reg, char *buf, int count) +{ + int bytes; + lgExtent_t ext[1]; + uint32_t pars[] = {handle, reg, count}; + + ext[0].size = sizeof(pars); + ext[0].count = sizeof(pars)/sizeof(pars[0]); + ext[0].bytes = sizeof(pars[0]); + ext[0].ptr = &pars; + + bytes = lg_command(sbc, LG_CMD_I2CRI, 1, ext, 0); + + if (bytes > 0) + { + bytes = recvMax(sbc, buf, count, bytes); + } + + _pmu(sbc); + + return bytes; +} + + +int i2c_write_i2c_block_data( + int sbc, int handle, int reg, const char *buf, int count) +{ + lgExtent_t ext[2]; + uint32_t pars[] = {handle, reg}; + + ext[0].size = sizeof(pars); + ext[0].count = sizeof(pars)/sizeof(pars[0]); + ext[0].bytes = sizeof(pars[0]); + ext[0].ptr = &pars; + + ext[1].size = count; + ext[1].count = count; + ext[1].bytes = 1; + ext[1].ptr = buf; + + return lg_command(sbc, LG_CMD_I2CWI, 2, ext, 1); +} + +int i2c_read_device(int sbc, int handle, char *buf, int count) +{ + int bytes; + + bytes = lg_command_2(sbc, LG_CMD_I2CRD, handle, count, 0); + + if (bytes > 0) + { + bytes = recvMax(sbc, buf, count, bytes); + } + + _pmu(sbc); + + return bytes; +} + +int i2c_write_device(int sbc, int handle, const char *buf, int count) +{ + lgExtent_t ext[2]; + uint32_t pars[] = {handle}; + + ext[0].size = sizeof(pars); + ext[0].count = sizeof(pars)/sizeof(pars[0]); + ext[0].bytes = sizeof(pars[0]); + ext[0].ptr = &pars; + + ext[1].size = count; + ext[1].count = count; + ext[1].bytes = 1; + ext[1].ptr = buf; + + return lg_command(sbc, LG_CMD_I2CWD, 2, ext, 1); +} + +int i2c_zip( + int sbc, int handle, const char *inBuf, int inLen,char *outBuf, int outLen) +{ + int bytes; + lgExtent_t ext[2]; + uint32_t pars[] = {handle}; + + ext[0].size = sizeof(pars); + ext[0].count = sizeof(pars)/sizeof(pars[0]); + ext[0].bytes = sizeof(pars[0]); + ext[0].ptr = &pars; + + ext[1].size = inLen; + ext[1].count = inLen; + ext[1].bytes = 1; + ext[1].ptr = inBuf; + + bytes = lg_command(sbc, LG_CMD_I2CZ, 2, ext, 0); + + if (bytes > 0) + { + bytes = recvMax(sbc, outBuf, outLen, bytes); + } + + _pmu(sbc); + + return bytes; +} + + +/* NOTIFICATIONS */ + +int notify_open(int sbc) + {return lg_command_0(sbc, LG_CMD_NO, 1);} + +int notify_resume(int sbc, int handle) + {return lg_command_1(sbc, LG_CMD_NR, handle, 1);} + +int notify_pause(int sbc, int handle) + {return lg_command_1(sbc, LG_CMD_NP, handle, 1);} + +int notify_close(int sbc, int handle) + {return lg_command_1(sbc, LG_CMD_NC, handle, 1);} + + +/* SCRIPTS */ + +int script_store(int sbc, const char *script) +{ + int len; + lgExtent_t ext[1]; + + len = strlen(script); + + if (!len) return 0; + + ext[0].size = len; + ext[0].count = len; + ext[0].bytes = 1; + ext[0].ptr = script; + + return lg_command(sbc, LG_CMD_PROC, 1, ext, 1); +} + +int script_run(int sbc, int handle, int count, const uint32_t *param) +{ + lgExtent_t ext[2]; + uint32_t pars[] = {handle}; + + ext[0].size = sizeof(pars); + ext[0].count = sizeof(pars)/sizeof(pars[0]); + ext[0].bytes = sizeof(pars[0]); + ext[0].ptr = &pars; + + ext[1].size = 4 * count; + ext[1].count = count; + ext[1].bytes = 4; + ext[1].ptr = param; + + return lg_command(sbc, LG_CMD_PROCR, 2, ext, 1); +} + +int script_update(int sbc, int handle, int count, const uint32_t *param) +{ + lgExtent_t ext[2]; + uint32_t pars[] = {handle}; + + ext[0].size = sizeof(pars); + ext[0].count = sizeof(pars)/sizeof(pars[0]); + ext[0].bytes = sizeof(pars[0]); + ext[0].ptr = &pars; + + ext[1].size = 4 * count; + ext[1].count = count; + ext[1].bytes = 4; + ext[1].ptr = param; + + return lg_command(sbc, LG_CMD_PROCU, 2, ext, 1); +} + +int script_status(int sbc, int handle, uint32_t *param) +{ + int status; + uint32_t p[LG_MAX_SCRIPT_PARAMS+1]; /* space for script status */ + + status = lg_command_1(sbc, LG_CMD_PROCP, handle, 0); + + if (status > 0) + { + recvMax(sbc, p, sizeof(p), status); + status = p[0]; + if (param) memcpy(param, p+1, sizeof(p)-4); + } + + _pmu(sbc); + + return status; +} + +int script_stop(int sbc, int handle) + {return lg_command_1(sbc, LG_CMD_PROCS, handle, 1);} + +int script_delete(int sbc, int handle) + {return lg_command_1(sbc, LG_CMD_PROCD, handle, 1);} + +/* SERIAL */ + +int serial_open(int sbc, const char *dev, int baud, int flags) +{ + int len; + lgExtent_t ext[2]; + uint32_t pars[] = {baud, flags}; + + len = strlen(dev); + + ext[0].size = sizeof(pars); + ext[0].count = sizeof(pars)/sizeof(pars[0]); + ext[0].bytes = sizeof(pars[0]); + ext[0].ptr = &pars; + + ext[1].size = len; + ext[1].count = len; + ext[1].bytes = 1; + ext[1].ptr = dev; + + return lg_command(sbc, LG_CMD_SERO, 2, ext, 1); +} + +int serial_close(int sbc, int handle) + {return lg_command_1(sbc, LG_CMD_SERC, handle, 1);} + +int serial_write_byte(int sbc, int handle, int val) + {return lg_command_2(sbc, LG_CMD_SERWB, handle, val, 1);} + +int serial_read_byte(int sbc, int handle) + {return lg_command_1(sbc, LG_CMD_SERRB, handle, 1);} + +int serial_write(int sbc, int handle, const char *buf, int count) +{ + lgExtent_t ext[2]; + uint32_t pars[] = {handle}; + + ext[0].size = sizeof(pars); + ext[0].count = sizeof(pars)/sizeof(pars[0]); + ext[0].bytes = sizeof(pars[0]); + ext[0].ptr = &pars; + + ext[1].size = count; + ext[1].count = count; + ext[1].bytes = 1; + ext[1].ptr = buf; + + return lg_command(sbc, LG_CMD_SERW, 2, ext, 1); +} + +int serial_read(int sbc, int handle, char *buf, int count) +{ + int bytes; + + bytes = lg_command_2(sbc, LG_CMD_SERR, handle, count, 0); + + if (bytes > 0) + { + bytes = recvMax(sbc, buf, count, bytes); + } + + _pmu(sbc); + + return bytes; +} + +int serial_data_available(int sbc, int handle) + {return lg_command_1(sbc, LG_CMD_SERDA, handle, 1);} + +/* SHELL */ + +int shell(int sbc, const char *scriptName, const char *scriptString) +{ + lgExtent_t ext[3]; + uint32_t ln, ls; + + ln = strlen(scriptName)+1; + ls = strlen(scriptString)+1; + + ext[0].size = 4; + ext[0].count = 1; + ext[0].bytes = 4; + ext[0].ptr = &ln; + + ext[1].size = ln; /* include null byte */ + ext[1].count = ln; + ext[1].bytes = 1; + ext[1].ptr = scriptName; + + ext[2].size = ls; /* include null byte */ + ext[2].count = ls; + ext[2].bytes = 1; + ext[2].ptr = scriptString; + + return lg_command(sbc, LG_CMD_SHELL, 3, ext, 1); +} + + +/* SPI */ + +int spi_open(int sbc, + int device, int channel, int speed, int flags) +{ + lgExtent_t ext[1]; + uint32_t pars[] = {device, channel, speed, flags}; + + ext[0].size = sizeof(pars); + ext[0].count = sizeof(pars)/sizeof(pars[0]); + ext[0].bytes = sizeof(pars[0]); + ext[0].ptr = &pars; + + return lg_command(sbc, LG_CMD_SPIO, 1, ext, 1); +} + +int spi_close(int sbc, int handle) + {return lg_command_1(sbc, LG_CMD_SPIC, handle, 1);} + +int spi_read(int sbc, int handle, char *buf, int count) +{ + int bytes; + + bytes = lg_command_2(sbc, LG_CMD_SPIR, handle, count, 0); + + if (bytes > 0) + { + bytes = recvMax(sbc, buf, count, bytes); + } + + _pmu(sbc); + + return bytes; +} + +int spi_write(int sbc, int handle, const char *buf, int count) +{ + lgExtent_t ext[2]; + uint32_t pars[] = {handle}; + + ext[0].size = sizeof(pars); + ext[0].count = sizeof(pars)/sizeof(pars[0]); + ext[0].bytes = sizeof(pars[0]); + ext[0].ptr = &pars; + + ext[1].size = count; + ext[1].count = count; + ext[1].bytes = 1; + ext[1].ptr = buf; + + return lg_command(sbc, LG_CMD_SPIW, 2, ext, 1); +} + +int spi_xfer(int sbc, int handle, const char *txBuf, char *rxBuf, int count) +{ + int bytes; + lgExtent_t ext[2]; + uint32_t pars[] = {handle}; + + ext[0].size = sizeof(pars); + ext[0].count = sizeof(pars)/sizeof(pars[0]); + ext[0].bytes = sizeof(pars[0]); + ext[0].ptr = &pars; + + ext[1].size = count; + ext[1].count = count; + ext[1].bytes = 1; + ext[1].ptr = txBuf; + + bytes = lg_command(sbc, LG_CMD_SPIX, 2, ext, 0); + + if (bytes > 0) + { + bytes = recvMax(sbc, rxBuf, count, bytes); + } + + _pmu(sbc); + + return bytes; +} + +/* THREADS */ + +pthread_t *thread_start(lgThreadFunc_t thread_func, void *userdata) +{ + pthread_t *pth; + pthread_attr_t pthAttr; + + pth = malloc(sizeof(pthread_t)); + + if (pth) + { + if (pthread_attr_init(&pthAttr)) + { + perror("pthread_attr_init failed"); + free(pth); + return NULL; + } + + if (pthread_attr_setstacksize(&pthAttr, STACK_SIZE)) + { + perror("pthread_attr_setstacksize failed"); + free(pth); + return NULL; + } + + if (pthread_create(pth, &pthAttr, thread_func, userdata)) + { + perror("pthread_create failed"); + free(pth); + return NULL; + } + } + + return pth; +} + +void thread_stop(pthread_t *pth) +{ + if (pth) + { + pthread_cancel(*pth); + pthread_join(*pth, NULL); + free(pth); + } +} + +/* UTILITIES */ + +int lgu_get_internal(int sbc, int config_id, uint64_t *config_value) +{ + int status; + int bytes; + lgExtent_t ext[1]; + uint32_t pars[] = {config_id}; + + ext[0].size = sizeof(pars); + ext[0].count = sizeof(pars)/sizeof(pars[0]); + ext[0].bytes = sizeof(pars[0]); + ext[0].ptr = &pars; + + bytes = lg_command(sbc, LG_CMD_CGI, 1, ext, 0); + + if (bytes > 0) + { + recvMax(sbc, config_value, 8, bytes); + status = LG_OKAY; + } + else status = bytes; + + _pmu(sbc); + + return status; +} + +int lgu_set_internal(int sbc, int config_id, uint64_t config_value) +{ + lgExtent_t ext[2]; + uint64_t parsQ[] = {config_value}; + uint32_t parsI[] = {config_id}; + + ext[0].size = sizeof(parsQ); + ext[0].count = sizeof(parsQ)/sizeof(parsQ[0]); + ext[0].bytes = sizeof(parsQ[0]); + ext[0].ptr = &parsQ; + + ext[1].size = sizeof(parsI); + ext[1].count = sizeof(parsI)/sizeof(parsI[0]); + ext[1].bytes = sizeof(parsI[0]); + ext[1].ptr = &parsI; + + return lg_command(sbc, LG_CMD_CSI, 2, ext, 1); +} + +int lgu_get_sbc_name(int sbc, char *name, int count) +{ + int bytes; + + bytes = lg_command_0(sbc, LG_CMD_SBC, 0); + + if (bytes > 0) + { + bytes = recvMax(sbc, name, count, bytes); + name[count-1] = 0; /* make sure null terminated */ + } + + _pmu(sbc); + + return bytes; +} + +int lgu_set_user(int sbc, char *user, char *secretFile) +{ + lgExtent_t ext[1]; + int len; + int bytes; + char hash[34]; + char salt1[LG_SALT_LEN]; + char salt2[LG_SALT_LEN]; + char buf[64]; + + hash[0] = 0; + + if (strlen(user) == 0) user = LG_DEFAULT_USER; + + snprintf(salt1, LG_SALT_LEN, "%015"PRIx64, xMakeSalt()); + sprintf(buf, "%s.%s", salt1, user); + + len = strlen(buf); + + ext[0].size = len; + ext[0].count = len; + ext[0].bytes = 1; + ext[0].ptr = buf; + + bytes = lg_command(sbc, LG_CMD_USER, 1, ext, 0); + + if (bytes < 0) return bytes; + + recvMax(sbc, salt2, LG_SALT_LEN, bytes); + + _pmu(sbc); + + lgMd5UserHash(user, salt1, salt2, secretFile, hash); + + len = strlen(hash); + + ext[0].size = len; + ext[0].count = len; + ext[0].bytes = 1; + ext[0].ptr = hash; + + return lg_command(sbc, LG_CMD_PASSW, 1, ext, 1); +} + +int lgu_set_share_id(int sbc, int handle, int share_id) + {return lg_command_2(sbc, LG_CMD_SHRS, handle, share_id, 1);} + +int lgu_use_share_id(int sbc, int share_id) + {return lg_command_1(sbc, LG_CMD_SHRU, share_id, 1);} + +uint32_t lgu_rgpio_version(void) + {return RGPIO_VERSION;} + +const char *lgu_error_text(int errnum) +{ + if (errnum > -1000) return lgErrStr(errnum); + else + { + switch(errnum) + { + case lgif_bad_send: + return "failed to send to rgpiod"; + case lgif_bad_recv: + return "failed to receive from rgpiod"; + case lgif_bad_getaddrinfo: + return "failed to find address of rgpiod"; + case lgif_bad_connect: + return "failed to connect to rgpiod"; + case lgif_bad_socket: + return "failed to create socket"; + case lgif_bad_noib: + return "failed to open notification in band"; + case lgif_duplicate_callback: + return "identical callback exists"; + case lgif_bad_malloc: + return "failed to malloc"; + case lgif_bad_callback: + return "bad callback parameter"; + case lgif_notify_failed: + return "failed to create notification thread"; + case lgif_callback_not_found: + return "callback not found"; + case lgif_unconnected_sbc: + return "not connected to sbc"; + case lgif_too_many_pis: + return "too many connected sbcs"; + + default: + return "unknown error"; + } + } +} + +void lgu_sleep(double sleepSecs) +{ + struct timespec ts, rem; + + if (sleepSecs > 0.0) + { + ts.tv_sec = sleepSecs; + ts.tv_nsec = (sleepSecs-(double)ts.tv_sec) * 1E9; + + while (clock_nanosleep(CLOCK_REALTIME, 0, &ts, &rem)) ts = rem; + } +} + +double lgu_time(void) + {return (double)lgu_timestamp() / (double)1E9;} + +uint64_t lgu_timestamp(void) +{ + struct timespec xts; + + clock_gettime(CLOCK_REALTIME, &xts); // get current time + + return ((uint64_t)1E9 * xts.tv_sec) + xts.tv_nsec; +} + diff --git a/rgpio.h b/rgpio.h new file mode 100644 index 0000000..3702a62 --- /dev/null +++ b/rgpio.h @@ -0,0 +1,2933 @@ +/* +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 +*/ + +#ifndef RGPIO_H +#define RGPIO_H + +#include +#include + +#include "lgpio.h" + +#define RGPIO_VERSION 0x00000000 + +/*TEXT + +rgpio is a C library 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 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 +o a simple interface to start and stop new threads + +*Usage* + +Include in your source files. + +Assuming your source is in prog.c use the following command to build + +. . +gcc -Wall -o prog prog.c -lrgpio +. . + +to run make sure the rgpiod daemon is running + +. . +rgpiod& + + ./prog +. . + +For examples see the lg archive file. + +*Notes* + +All the functions which return an int return < 0 on error + +TEXT*/ + +/*OVERVIEW + +ESSENTIAL + +rgpiod_start Connects to a rgpiod daemon +rgpiod_stop Disconnects from a rgpiod daemon + +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 Gets gpiochip information +gpio_get_line_info Gets gpiochip line information +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_time Sets the debounce time for a GPIO +gpio_set_watchdog_time Sets the watchdog time for a GPIO + +callback Starts a GPIO callback +callback_cancel Stops 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 Start notifications for selected GPIO + +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 + +THREADS + +thread_start Start a new thread +thread_stop Stop a previously started thread + +UTILITIES + +lgu_get_sbc_name Get the SBC name + +lgu_get_internal Get a SBC configuration value +lgu_set_internal Set a SBC configuration value + +lgu_time Returns the number of seconds since the epoch +lgu_timestamp Returns the number of nanoseconds since the epoch + +lgu_sleep Sleeps for a number of seconds + +lgu_set_user Set the user (and associated permissions) + +lgu_set_share_id Set the share id for a resource +lgu_use_share_id Use this share id when asking for a resource + +lgu_rgpio_version Get the rgpio library version +lgu_error_text Get the error text for an error code + +OVERVIEW*/ + + +#ifdef __cplusplus +extern "C" { +#endif + +#define RISING_EDGE 1 +#define FALLING_EDGE 2 +#define BOTH_EDGES 3 + +typedef void (*CBFunc_t) + (int sbc, int chip, int gpio, + int level, uint64_t tick, void *userdata); + +typedef struct callback_s callback_t; + +typedef void *(lgThreadFunc_t) (void *); + +/* --------------------------------------------------------- ESSENTIAL API +*/ + +/*F*/ +int rgpiod_start(const char *addrStr, const char *portStr); +/*D +Connect to the rgpiod daemon. Reserving command and +notification streams. + +. . +addrStr: specifies the host or IP address of the SBC running the + rgpiod daemon. It may be NULL in which case localhost + is used unless overridden by the LG_ADDR environment + variable. + +portStr: specifies the port address used by the SBC running the + rgpiod daemon. It may be NULL in which case "8889" + is used unless overridden by the LG_PORT environment + variable. +. . + +If OK returns a sbc (>= 0). + +On failure returns a negative error code. + +This sbc value is passed to the other functions to specify +the SBC to be operated on. +D*/ + +/*F*/ +void rgpiod_stop(int sbc); +/*D +Terminates the connection to a rgpiod daemon and frees +resources used by the library. + +. . +sbc: >= 0 (as returned by [*rgpiod_start*]). +. . +D*/ + + +/* -------------------------------------------------------------- FILE API +*/ + +#pragma GCC diagnostic push + +#pragma GCC diagnostic ignored "-Wcomment" + +/*F*/ +int file_open(int sbc, const char *file, int mode); +/*D +This function returns a handle to a file opened in a specified mode. + +This is a privileged command. See [+permits+]. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +file: the file to open. +mode: the file open mode. +. . + +If OK returns a handle (>= 0). + +On failure returns a negative error code. + +File + +A file may only be opened if permission is granted by an entry in +the [files] section of the permits file. This is intended to allow +remote access to files in a controlled manner. + +Mode + +The mode may have the following values. + +Macro @ Value @ Meaning +LG_FILE_READ @ 1 @ open file for reading +LG_FILE_WRITE @ 2 @ open file for writing +LG_FILE_RW @ 3 @ open file for reading and writing + +The following values may be or'd into the mode. + +Macro @ Value @ Meaning +LG_FILE_APPEND @ 4 @ Writes append data to the end of the file +LG_FILE_CREATE @ 8 @ The file is created if it doesn't exist +LG_FILE_TRUNC @ 16 @ The file is truncated + +Newly created files are owned by the user who launched the daemon +with permissions owner read and write. + +... +#include +#include + +int main(int argc, char *argv[]) +{ + int sbc, handle, c; + char buf[60000]; + + sbc = rgpiod_start(NULL, NULL); + + if (sbc < 0) return 1; + + handle = file_open(sbc, "/ram/lg.c", LG_FILE_READ); + + if (handle >= 0) + { + while ((c=file_read(sbc, handle, buf, sizeof(buf)-1))) + { + buf[c] = 0; + printf("%s", buf); + } + + file_close(sbc, handle); + } + + rgpiod_stop(sbc); +} +... +D*/ + +#pragma GCC diagnostic pop + +/*F*/ +int file_close(int sbc, int handle); +/*D +This function closes the file. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +handle: >= 0 (as returned by [*file_open*]). +. . + +If OK returns 0. + +On failure returns a negative error code. + +... +file_close(sbc, handle); +... +D*/ + + +/*F*/ +int file_write(int sbc, int handle, const char *buf, int count); +/*D +This function writes count bytes from buf to the the file. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +handle: >= 0 (as returned by [*file_open*]). + buf: the array of bytes to write. + count: the number of bytes to write. +. . + +If OK returns 0. + +On failure returns a negative error code. + +... +if (file_write(sbc, handle, buf, 100) == 0) +{ + // file written okay +} +else +{ + // error +} +... +D*/ + + +/*F*/ +int file_read(int sbc, int handle, char *buf, int count); +/*D +This function reads up to count bytes from the the file. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +handle: >= 0 (as returned by [*file_open*]). + buf: an array to receive the read data. + count: the maximum number of bytes to read. +. . + +If OK returns the count of bytes read and updates buf. + +On failure returns a negative error code. + +... + bytes = file_read(sbc, handle, buf, sizeof(buf)); + + if (bytes >= 0) + { + // process read data + } +... +D*/ + + +/*F*/ +int file_seek(int sbc, int handle, int32_t seekOffset, int seekFrom); +/*D +This function seeks to a position within the file. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). + handle: >= 0 (as returned by [*file_open*]). +seekOffset: the number of bytes to move. Positive offsets + move forward, negative offsets backwards. + seekFrom: one of LG_FROM_START (0), LG_FROM_CURRENT (1), + or LG_FROM_END (2). +. . + +If OK returns the new file position. + +On failure returns a negative error code. + +... +file_seek(sbc, handle, 123, LG_FROM_START); // Start plus 123 + +size = file_seek(sbc, handle, 0, LG_FROM_END); // End, return size + +pos = file_seek(sbc, handle, 0, LG_FROM_CURRENT); // Current position +... +D*/ + +#pragma GCC diagnostic push + +#pragma GCC diagnostic ignored "-Wcomment" + +/*F*/ +int file_list(int sbc, const char *fpat, char *buf, int count); +/*D +This function returns a list of files which match a pattern. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). + fpat: file pattern to match. + buf: an array to receive the matching file names. +count: the maximum number of bytes to read. +. . + +If OK returns the count of bytes read and updates buf with +the matching filenames (the filenames are separated by newline +characters). + +On failure returns a negative error code. + +... +#include +#include + +int main(int argc, char *argv[]) +{ + int sbc, handle, c; + char buf[60000]; + + sbc = rgpiod_start(NULL, NULL); + + if (sbc < 0) return 1; + + c = file_list(sbc, "/ram/p*.c", buf, sizeof(buf)); + + if (c >= 0) + { + buf[c] = 0; + printf("%s", buf); + } + + rgpiod_stop(sbc); +} +... +D*/ + +#pragma GCC diagnostic pop + +/* -------------------------------------------------------------- GPIO API +*/ + +/*F*/ +int gpiochip_open(int sbc, int gpioDev); +/*D +This returns a handle to a gpiochip device. + +This is a privileged command. See [+permits+]. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +gpioDev: >= 0 +. . + +If OK returns a handle (>= 0). + +On failure returns a negative error code. + +... +h = gpiochip_open(sbc, 0); // open gpiochip0 + +if (h >= 0) +{ + // open ok +} +else +{ + // open error +} +... + +D*/ + +/*F*/ +int gpiochip_close(int sbc, int handle); +/*D +This closes a gpiochip device. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +handle: >= 0 (as returned by [*gpiochip_open*]). +. . + +If OK returns 0. + +On failure returns a negative error code. + +... +status = gpiochip_close(sbc, h); // close gpiochip + +if (status < 0) +{ + // close failed +} +... +D*/ + +/*F*/ +int gpio_get_chip_info(int sbc, int handle, lgChipInfo_p chipInfo); +/*D +This returns summary information of an opened gpiochip. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). + handle: >= 0 (as returned by [*gpiochip_open*]). +chipInfo: address to store returned chip info. +. . + +If OK returns a list of okay status, number of +lines, name, and label. + +On failure returns a negative error code. +D*/ + +/*F*/ +int gpio_get_line_info(int sbc, int handle, int gpio, lgLineInfo_p lineInfo); +/*D +This returns detailed information of a GPIO of +an opened gpiochip. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). + handle: >= 0 (as returned by [*gpiochip_open*]). + gpio: the GPIO. +lineInfo: address to store returned line info. +. . + +If OK returns a list of okay status, offset, +line flags, name, and user. + +On failure returns a negative error code. +D*/ + +/*F*/ +int gpio_get_mode(int sbc, int handle, int gpio); +/*D +This returns the mode of a GPIO. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +handle: >= 0 (as returned by [*gpiochip_open*]). + gpio: the GPIO to be read. +. . + +If OK returns the mode of the GPIO. + +On failure returns a negative error code. + +Mode 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: --- +6 @ 64 @ Kernel: --- +7 @ 128 @ Kernel: --- +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: --- +D*/ + +/*F*/ +int gpio_claim_input(int sbc, int handle, int lFlags, int gpio); +/*D +This claims a GPIO for input. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +handle: >= 0 (as returned by [*gpiochip_open*]). +lFlags: line flags for the GPIO. + gpio: the GPIO to be claimed. +. . + +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, or open source. + +... +status = gpio_claim_input(sbc, h, 0, 23); // open GPIO 23 for input +... +D*/ + + +/*F*/ +int gpio_claim_output( + int sbc, int handle, int lFlags, int gpio, int value); +/*D +This claims a GPIO for output. +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +handle: >= 0 (as returned by [*gpiochip_open*]). +lFlags: line flags for the GPIO. + gpio: the GPIO to be claimed. + value: the initial value 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, or open source. + +If value is zero the GPIO will be initialised low (0). If any other +value is used the GPIO will be initialised high (1). + +... +status = gpio_claim_output(sbc, h, 0, 35, 1); // open GPIO 35 for high output +... +D*/ + + +/*F*/ +int gpio_free(int sbc, int handle, int gpio); +/*D +This frees a GPIO. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +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. +D*/ + + +/*F*/ +int group_claim_input( + int sbc, int handle, int lFlags, int count, const int *gpios); +/*D +This claims a group of GPIO for inputs. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +handle: >= 0 (as returned by [*gpiochip_open*]). +lFlags: line flags for each GPIO. + count: the number of GPIO to claim. + gpios: the group 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, or open source. + +gpios is an array of one or more GPIO. The first GPIO in the array +is called the group leader and is used to reference the group as a whole. +D*/ + +/*F*/ +int group_claim_output( + int sbc, int handle, int lFlags, + int count, const int *gpios, const int *values); +/*D +This claims a group of GPIO to be used as outputs. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +handle: >= 0 (as returned by [*gpiochip_open*]). +lFlags: line flags for each GPIO. + count: the number of GPIO to claim. + gpios: the group GPIO. +values: the initial value for each 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, or open source. + +gpios is an array of one or more GPIO. The first GPIO in the array is +called the group leader and is used to reference the group as a whole. + +values 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). +D*/ + +/*F*/ +int group_free(int sbc, int handle, int gpio); +/*D +This frees all the group GPIO. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +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. +D*/ + +/*F*/ +int gpio_read(int sbc, int handle, int gpio); +/*D +This returns the level of a GPIO. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +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. +D*/ + +/*F*/ +int gpio_write(int sbc, int handle, int gpio, int value); +/*D +This sets the level of an output GPIO. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +handle: >= 0 (as returned by [*gpiochip_open*]). + gpio: the GPIO to be written. + value: 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). +D*/ + +/*F*/ +int group_read( + int sbc, int handle, int gpio, uint64_t *groupBits); +/*D +This returns the levels read from a group. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). + handle: >= 0 (as returned by [*gpiochip_open*]). + gpio: the offset of a member of the GPIO group to be read. +groupBits: a pointer to a 64-bit memory area for the returned value. +. . + +If OK returns the group size and updates groupBits. + +On failure returns a negative error code. + +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 groupBits 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. +D*/ + +/*F*/ +int group_write( + int sbc, int handle, int gpio, uint64_t groupBits, uint64_t groupMask); +/*D +This sets the levels of an output output group. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). + handle: >= 0 (as returned by [*gpiochip_open*]). + gpio: the offset of a member of the GPIO group to be written. +groupBits: the level to set if the corresponding bit in groupMask is set. +groupMask: 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. +D*/ + +/*F*/ +int tx_pulse( + int sbc, int handle, int gpio, + int pulse_on, int pulse_off, + int pulse_offset, int pulse_cycles); +/*D +This starts software timed pulses on an output GPIO. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). + handle: >= 0 (as returned by [*gpiochip_open*]). + gpio: GPIO to be written. + pulse_on: pulse high time in microseconds. + pulse_off: pulse low time in microseconds. +pulse_offset: offset from nominal pulse start position. +pulse_cycles: the number of pulses 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 duty cycle +(percentage of high time per cycle). + +The set frequency will be 1000000 / (pulse_on + pulse_off) Hz. + +The set duty cycle 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 duty cycle 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 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 pulse settings may be queued in this way. +D*/ + +/*F*/ +int tx_pwm( + int sbc, + int handle, + int gpio, + float pwmFrequency, + float pwmDutyCycle, + int pwmOffset, + int pwmCycles); +/*D +This starts software timed PWM on an output GPIO. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). + handle: >= 0 (as returned by [*gpiochip_open*]) + gpio: the GPIO to be pulsed +pwmFrequency: PWM frequency in Hz (0=off, 0.1-10000) +pwmDutyCycle: PWM duty cycle in % (0-100) + pwmOffset: offset from nominal pulse start position + pwmCycles: the number of pulses 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. + +PWM is characterised by two values, its frequency (number of cycles +per second) and its duty cycle (percentage of high time per cycle). + +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 compete. + +Multiple PWM settings may be queued in this way. +D*/ + +/*F*/ +int tx_servo( + int sbc, + int handle, + int gpio, + int pulseWidth, + int servoFrequency, + int servoOffset, + int servoCycles); +/*D +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 + pulseWidth: pulse high time in microseconds (0=0ff, 500-2500) +servoFrequency: the number of pulses per second (40-500) + servoOffset: offset from nominal pulse start position + servoCycles: the number of pulses 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. + +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 complete. + +Multiple servo settings may be queued in this way. +D*/ + + +/*F*/ +int tx_wave( + int sbc, int handle, int gpio, int count, lgPulse_p pulses); +/*D +This starts a software timed wave on an output group. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +handle: >= 0 (as returned by [*gpiochip_open*]). + gpio: group leader. + count: the number of pulses in the wave. +pulses: the pulses. +. . + +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. +D*/ + +/*F*/ +int tx_busy(int sbc, int handle, int gpio, int kind); +/*D +This returns true if transmissions of the specified kind +are active on the GPIO or group. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +handle: >= 0 (as returned by [*gpiochip_open*]). + gpio: the GPIO or group to be tested. + kind: LG_TX_PWM or LG_TX_WAVE. +. . + +If OK returns 1 for busy and 0 for not busy. + +On failure returns a negative error code. + +D*/ + +/*F*/ +int tx_room(int sbc, int handle, int gpio, int kind); +/*D +This returns the number of entries there are to queue further +transmissions of the specified kind on a GPIO or GPIO group. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +handle: >= 0 (as returned by [*gpiochip_open*]). + gpio: the GPIO or group to be tested. + kind: LG_TX_PWM or LG_TX_WAVE. +. . + +If OK returns the number of free entries (0 for none). + +On failure returns a negative error code. + +D*/ + +/*F*/ +int gpio_set_debounce_time(int sbc, int handle, int gpio, int debounce_us); +/*D +This sets the debounce time for a GPIO. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). + handle: >= 0 (as returned by [*gpiochip_open*]). + gpio: the GPIO to be configured. +debounce_us: the debounce time in microseconds. +. . + +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. +D*/ + +/*F*/ +int gpio_set_watchdog_time(int sbc, int handle, int gpio, int watchdog_us); +/*D +This sets the watchdog time for a GPIO. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). + handle: >= 0 (as returned by [*gpiochip_open*]). + gpio: the GPIO to be configured. +watchdog_us: the watchdog time in microseconds. +. . + +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 LG_TIMEOUT (2) for a watchdog alert. +D*/ + + +/*F*/ +int gpio_claim_alert( + int sbc, int handle, int lFlags, int eFlags, int gpio, int nfyHandle); +/*D +This claims a GPIO to be used as a source of alerts on level changes. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). + handle: >= 0 (as returned by [*gpiochip_open*]). + gpio: >= 0, as legal for the gpiochip. + lFlags: line flags for the GPIO. + eFlags: event flags for the GPIO. +nfyHandle: >=0, a notification handle (use -1 for callbacks). +. . + +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, or open source. + +The event flags are used to generate alerts for a rising edge, +falling edge, or both edges. + +Use a notification handle of -1 unless you plan to read the alerts +from a notification pipe you have opened. +D*/ + +/*F*/ +int callback + (int sbc, int handle, int gpio, int edge, CBFunc_t f, void *userdata); +/*D +This function initialises a new callback. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). + handle: >= 0,(as returned by [*gpiochip_open*]). + gpio: >= 0, as legal for the gpiochip. + edge: RISING_EDGE, FALLING_EDGE, or BOTH_EDGES. + f: the callback function. +userdata: a pointer to arbitrary user data. +. . + +If OK returns a callback id. + +On failure returns a negative error code. + +The user supplied callback receives the chip, GPIO, edge, timestamp, +and the userdata pointer, whenever the GPIO has the identified edge. + +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) + +The timestamp is when the change happened reported as the +number of nanoseconds since the epoch (start of 1970). + +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. +D*/ + +/*F*/ +int callback_cancel(int callback_id); +/*D +This function cancels a callback identified by its id. + +. . +callback_id: >= 0 (as returned by [*callback*]). +. . + +If OK returns 0. + +On failure returns a negative error code. + +D*/ + + +/* --------------------------------------------------------------- I2C API +*/ + +/*F*/ +int i2c_open(int sbc, int i2c_bus, int i2c_addr, int i2c_flags); +/*D +This returns a handle for the device at address i2c_addr on bus i2c_bus. + +This is a privileged command. See [+permits+]. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). + i2c_bus: >= 0. + i2c_addr: 0-0x7F. +i2c_flags: 0. +. . + +If OK returns a handle (>= 0). + +On failure returns a negative error code. + +No flags are currently defined. This parameter should be set to zero. + +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. +i2c_reg (8 bits): A 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. +. . +D*/ + +/*F*/ +int i2c_close(int sbc, int handle); +/*D +This closes the I2C device + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +handle: >= 0 (as returned by [*i2c_open*]). +. . + +If OK returns 0. + +On failure returns a negative error code. +D*/ + +/*F*/ +int i2c_write_quick(int sbc, int handle, int bitVal); +/*D +This sends a single bit (in the Rd/Wr bit) to the device. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +handle: >= 0 (as returned by [*i2c_open*]). +bitVal: 0-1, the value to write. +. . + +If OK returns 0. + +On failure returns a negative error code. + +Quick command. SMBus 2.0 5.5.1 +. . +S Addr bit [A] P +. . +D*/ + +/*F*/ +int i2c_write_byte(int sbc, int handle, int byteVal); +/*D +This sends a single byte to the device. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). + handle: >= 0 (as returned by [*i2c_open*]). +byteVal: 0-0xFF, the value to write. +. . + +If OK returns 0. + +On failure returns a negative error code. + +Send byte. SMBus 2.0 5.5.2 +. . +S Addr Wr [A] byteVal [A] P +. . +D*/ + +/*F*/ +int i2c_read_byte(int sbc, int handle); +/*D +This reads a single byte from the device. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +handle: >= 0 (as returned by [*i2c_open*]). +. . + +If OK returns the byte read (0-255). + +On failure returns a negative error code. + +Receive byte. SMBus 2.0 5.5.3 +. . +S Addr Rd [A] [Data] NA P +. . +D*/ + +/*F*/ +int i2c_write_byte_data( + int sbc, int handle, int i2c_reg, int byteVal); +/*D +This writes a single byte to the specified register of the device. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). + handle: >= 0 (as returned by [*i2c_open*]). +i2c_reg: 0-255, the register to write. +byteVal: 0-0xFF, the value to write. +. . + +If OK returns 0. + +On failure returns a negative error code. + +Write byte. SMBus 2.0 5.5.4 +. . +S Addr Wr [A] i2c_reg [A] byteVal [A] P +. . +D*/ + +/*F*/ +int i2c_write_word_data( + int sbc, int handle, int i2c_reg, int wordVal); +/*D +This writes a single 16 bit word to the specified register of the device. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). + handle: >= 0 (as returned by [*i2c_open*]). +i2c_reg: 0-255, the register to write. +wordVal: 0-0xFFFF, the value to write. +. . + +If OK returns 0. + +On failure returns a negative error code. + +Write word. SMBus 2.0 5.5.4 +. . +S Addr Wr [A] i2c_reg [A] wval_Low [A] wVal_High [A] P +. . +D*/ + +/*F*/ +int i2c_read_byte_data(int sbc, int handle, int i2c_reg); +/*D +This reads a single byte from the specified register of the device. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). + handle: >= 0 (as returned by [*i2c_open*]). +i2c_reg: 0-255, the register to read. +. . + +If OK returns the read byte (0-255). + +On failure returns a negative error code. + +Read byte. SMBus 2.0 5.5.5 +. . +S Addr Wr [A] i2c_reg [A] S Addr Rd [A] [Data] NA P +. . +D*/ + +/*F*/ +int i2c_read_word_data(int sbc, int handle, int i2c_reg); +/*D +This reads a single 16 bit word from the specified register of the device. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). + handle: >= 0 (as returned by [*i2c_open*]). +i2c_reg: 0-255, the register to read. +. . + +If OK returns the read word (0-65535). + +On failure returns a negative error code. + +Read word. SMBus 2.0 5.5.5 +. . +S Addr Wr [A] i2c_reg [A] + S Addr Rd [A] [DataLow] A [DataHigh] NA P +. . +D*/ + +/*F*/ +int i2c_process_call(int sbc, int handle, int i2c_reg, int wordVal); +/*D +This writes 16 bits of data to the specified register of the device +and reads 16 bits of data in return. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). + handle: >= 0 (as returned by [*i2c_open*]). +i2c_reg: 0-255, the register to write/read. +wordVal: 0-0xFFFF, the value to write. +. . + +If OK returns the read word (0-65535). + +On failure returns a negative error code. + +Process call. SMBus 2.0 5.5.6 +. . +S Addr Wr [A] i2c_reg [A] wVal_Low [A] wVal_High [A] + S Addr Rd [A] [DataLow] A [DataHigh] NA P +. . +D*/ + +/*F*/ +int i2c_write_block_data( + int sbc, int handle, int i2c_reg, const char *buf, int count); +/*D +This writes up to 32 bytes to the specified register of the device. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). + handle: >= 0 (as returned by [*i2c_open*]). +i2c_reg: 0-255, the register to write. + buf: an array with the data to send. + count: 1-32, the number of bytes to write. +. . + +If OK returns 0. + +On failure returns a negative error code. + +Block write. SMBus 2.0 5.5.7 +. . +S Addr Wr [A] i2c_reg [A] count [A] buf0 [A] buf1 [A] ... + [A] bufn [A] P +. . +D*/ + +/*F*/ +int i2c_read_block_data(int sbc, int handle, int i2c_reg, char *buf); +/*D +This reads a block of up to 32 bytes from the specified register of +the device. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). + handle: >= 0 (as returned by [*i2c_open*]). +i2c_reg: 0-255, the register to read. + buf: an array to receive the read data. +. . + +If OK returns the count of bytes read and updates buf. + +On failure returns a negative error code. + +The amount of returned data is set by the device. + +Block read. SMBus 2.0 5.5.7 +. . +S Addr Wr [A] i2c_reg [A] + S Addr Rd [A] [Count] A [buf0] A [buf1] A ... A [bufn] NA P +. . +D*/ + +/*F*/ +int i2c_block_process_call( + int sbc, int handle, int i2c_reg, char *buf, int count); +/*D +This writes data bytes to the specified register of the device +and reads a device specified number of bytes of data in return. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). + handle: >= 0 (as returned by [*i2c_open*]). +i2c_reg: 0-255, the register to write/read. + buf: an array with the data to send and to receive the read data. + count: 1-32, the number of bytes to write. +. . + +If OK returns the count of bytes read and updates buf. + +On failure returns a negative error code. + +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. + +Block write-block read. SMBus 2.0 5.5.8 +. . +S Addr Wr [A] i2c_reg [A] count [A] buf0 [A] ... + S Addr Rd [A] [Count] A [Data] ... A P +. . +D*/ + +/*F*/ +int i2c_read_i2c_block_data( + int sbc, int handle, int i2c_reg, char *buf, int count); +/*D +This reads count bytes from the specified register of the device. +The count may be 1-32. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). + handle: >= 0 (as returned by [*i2c_open*]). +i2c_reg: 0-255, the register to read. + buf: an array to receive the read data. + count: 1-32, the number of bytes to read. +. . + +If OK returns the count of bytes read and updates buf. + +On failure returns a negative error code. + +. . +S Addr Wr [A] i2c_reg [A] + S Addr Rd [A] [buf0] A [buf1] A ... A [bufn] NA P +. . +D*/ + + +/*F*/ +int i2c_write_i2c_block_data( + int sbc, int handle, int i2c_reg, const char *buf, int count); +/*D +This writes 1 to 32 bytes to the specified register of the device. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). + handle: >= 0 (as returned by [*i2c_open*]). +i2c_reg: 0-255, the register to write. + buf: the data to write. + count: 1-32, the number of bytes to write. +. . + +If OK returns 0. + +On failure returns a negative error code. + +. . +S Addr Wr [A] i2c_reg [A] buf0 [A] buf1 [A] ... [A] bufn [A] P +. . +D*/ + +/*F*/ +int i2c_read_device(int sbc, int handle, char *buf, int count); +/*D +This reads count bytes from the raw device into buf. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +handle: >= 0 (as returned by [*i2c_open*]). + buf: an array to receive the read data bytes. + count: >0, the number of bytes to read. +. . + +If OK returns the count of bytes read and updates buf. + +On failure returns a negative error code. + +. . +S Addr Rd [A] [buf0] A [buf1] A ... A [bufn] NA P +. . +D*/ + +/*F*/ +int i2c_write_device(int sbc, int handle, const char *buf, int count); +/*D +This writes count bytes from buf to the raw device. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +handle: >= 0 (as returned by [*i2c_open*]). + buf: an array containing the data bytes to write. + count: >0, the number of bytes to write. +. . + +If OK returns 0. + +On failure returns a negative error code. + +. . +S Addr Wr [A] buf0 [A] buf1 [A] ... [A] bufn [A] P +. . +D*/ + +/*F*/ +int i2c_zip( + int sbc, int handle, + const char *inBuf, int inCount, char *outBuf, int outCount); +/*D +This function executes a sequence of I2C operations. The +operations to be performed are specified by the contents of inBuf +which contains the concatenated command codes and associated data. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). + handle: >= 0, as returned by a call to [*lgI2cOpen*] + inBuf: pointer to the concatenated I2C commands, see below + inCount: size of command buffer + outBuf: pointer to buffer to hold returned data +outCount: size of output buffer +. . + +If OK returns the count of bytes read and updates outBuf. + +On failure returns a negative error code. + +The following command codes are supported: + +Name @ Cmd & Data @ Meaning +End @ 0 @ No more commands +Escape @ 1 @ Next P is two bytes +On @ 2 @ Switch combined flag on +Off @ 3 @ Switch combined flag off +Address @ 4 P @ Set I2C address to P +Flags @ 5 lsb msb @ Set I2C flags to lsb + (msb << 8) +Read @ 6 P @ Read P bytes of data +Write @ 7 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. + +The returned I2C data is stored in consecutive locations of outBuf. + +... +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 + +0x04 0x53 0x07 0x01 0x32 0x06 0x06 +0x04 0x1E 0x07 0x01 0x03 0x06 0x06 +0x04 0x68 0x07 0x01 0x1B 0x06 0x08 +0x00 +... + +D*/ + + + +/* ----------------------------------------------------- NOTIFICATIONS API +*/ + +/*F*/ +int notify_open(int sbc); +/*D +Get a free notification handle. + +This is a privileged command. See [+permits+]. + +. . +sbc: >= 0 (as returned by [*rgpiod_start*]). +. . + +If OK returns a handle (>= 0). + +On failure returns a negative error code. + +A notification is a method for being notified of GPIO state +changes via a pipe. + +Pipes are only accessible from the local machine so this function +serves no purpose if you are using the library from a remote machine. +The in-built (socket) notifications provided by [*callback*] +should be used instead. + +The notification pipes are created in the library 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. + +Each notification occupies 16 bytes in the fifo and has the +following structure. + +. . +typedef struct +{ + uint64_t timestamp; // alert time in nanoseconds + uint8_t chip; // gpiochip device number + uint8_t gpio; // offset into gpio device + uint8_t level; // 0=low, 1=high, 2=timeout + uint8_t flags; // none currently defined +} lgGpioReport_t; +. . + +timestamp: the number of nanoseconds since the epoch (start of 1970) +chip: the gpiochip device number (NOT the handle). +gpio: the GPIO. +level: indicates the level of the GPIO +flags: no flags are currently defined + +For future proofing it is probably best to ignore any notification +with non-zero flags. + +D*/ + +/*F*/ +int notify_resume(int sbc, int handle); +/*D +Resume notifications on a handle. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +handle: >= 0 (as returned by [*notify_open*]) +. . + +If OK returns 0. + +On failure returns a negative error code. +D*/ + +/*F*/ +int notify_pause(int sbc, int handle); +/*D +Pauses notifications on a handle. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +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. +D*/ + +/*F*/ +int notify_close(int sbc, int handle); +/*D +Stop notifications and releases the handle. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +handle: >= 0 (as returned by [*notify_open*]) +. . + +If OK returns 0. + +On failure returns a negative error code. +D*/ + + + +/* ----------------------------------------------------------- SCRIPTS API +*/ + +/*F*/ +int script_store(int sbc, const char *script); +/*D +This function stores a script for later execution. + +This is a privileged command. See [+permits+]. + +See [[scripts.html]] for details. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +script: the text of the script. +. . + +If OK returns a handle (>=0). + +On failure returns a negative error code. +D*/ + +/*F*/ +int script_run(int sbc, int handle, int count, const uint32_t *param); +/*D +This function runs a stored script. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +handle: >= 0 (as returned by [*script_store*]). + count: 0-10, the number of parameters. + param: an array of parameters. +. . + +If OK returns 0. + +On failure returns a negative error code. + +param is an array of up to 10 parameters which may be referenced in +the script as p0 to p9. +D*/ + +/*F*/ +int script_update(int sbc, int handle, int count, const uint32_t *param); +/*D +This function sets the parameters of a script. The script may or +may not be running. The first numPar parameters of the script are +overwritten with the new values. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +handle: >= 0 (as returned by [*script_store*]). + count: 0-10, the number of parameters. + param: an array of parameters. +. . + +If OK returns 0. + +On failure returns a negative error code. + +param is an array of up to 10 parameters which may be referenced in +the script as p0 to p9. +D*/ + +/*F*/ +int script_status(int sbc, int handle, uint32_t *param); +/*D +This function returns the run status of a stored script as well +as the current values of parameters 0 to 9. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +handle: >= 0 (as returned by [*script_store*]). + param: an array to hold the returned 10 parameters. +. . + +If OK returns the script status and updates param. + +On failure returns a negative error code. + +The script status may be + +. . +LG_SCRIPT_INITING +LG_SCRIPT_READY +LG_SCRIPT_RUNNING +LG_SCRIPT_WAITING +LG_SCRIPT_ENDED +LG_SCRIPT_HALTED +LG_SCRIPT_FAILED +. . + +The current value of script parameters 0 to 9 are returned in param. +D*/ + +/*F*/ +int script_stop(int sbc, int handle); +/*D +This function stops a running script. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +handle: >= 0 (as returned by [*script_store*]). +. . + +If OK returns 0. + +On failure returns a negative error code. +D*/ + +/*F*/ +int script_delete(int sbc, int handle); +/*D +This function deletes a stored script. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +handle: >= 0 (as returned by [*script_store*]). +. . + +If OK returns 0. + +On failure returns a negative error code. +D*/ + +/* ------------------------------------------------------------ SERIAL API +*/ + +/*F*/ +int serial_open(int sbc, const char *ser_tty, int ser_baud, int ser_flags); +/*D +This function opens a serial device at a specified baud rate +with specified flags. The device must be present in /dev. + +This is a privileged command. See [+permits+]. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). + ser_tty: the serial device to open. + ser_baud: the baud rate in bits per second, see below. +ser_flags: 0. +. . + +If OK returns 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. + +No flags are currently defined. This parameter should be set to zero. +D*/ + +/*F*/ +int serial_close(int sbc, int handle); +/*D +This function closes the serial device. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +handle: >= 0 (as returned by [*serial_open*]). +. . + +If OK returns 0. + +On failure returns a negative error code. +D*/ + +/*F*/ +int serial_write_byte(int sbc, int handle, int byteVal); +/*D +This function writes byteVal to the serial port. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +handle: >= 0 (as returned by [*serial_open*]). +. . + +If OK returns 0. + +On failure returns a negative error code. +D*/ + +/*F*/ +int serial_read_byte(int sbc, int handle); +/*D +This function reads a byte from the serial port. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +handle: >= 0 (as returned by [*serial_open*]). +. . + +If OK returns the read byte (0-255). + +On failure returns a negative error code. +D*/ + +/*F*/ +int serial_write(int sbc, int handle, const char *buf, int count); +/*D +This function writes count bytes from buf to the the serial port. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +handle: >= 0 (as returned by [*serial_open*]). + buf: the array of bytes to write. + count: the number of bytes to write. +. . + +If OK returns 0. + +On failure returns a negative error code. +D*/ + +/*F*/ +int serial_read(int sbc, int handle, char *buf, int count); +/*D +This function reads up to count bytes from the the serial port +and writes them to buf. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +handle: >= 0 (as returned by [*serial_open*]). + buf: an array to receive the read data. + count: the maximum number of bytes to read. +. . + +If OK returns the count of bytes read and updates buf. + +On failure returns a negative error code. + +If no data is ready zero is returned. +D*/ + +/*F*/ +int serial_data_available(int sbc, int handle); +/*D +Returns the number of bytes available to be read from the +device. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +handle: >= 0 (as returned by [*serial_open*]). +. . + +If OK returns the count of bytes available. + +On failure returns a negative error code. +D*/ + +/* ------------------------------------------------------------- SHELL API +*/ + +/*F*/ +int shell(int sbc, const char *scriptName, const char *scriptString); +/*D +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+]. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). + scriptName: the name of the script, only alphanumeric characters, + '-' and '_' are allowed in the name. +scriptString: the string to pass to the script. +. . + +If OK returns 0. + +On failure returns a negative error code. + +scriptName 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 = shell_(sbc, "scr1", "hello world"); + +// pass three parameters, hello, string with spaces, and world +status = shell_(sbc, "scr1", "hello 'string with spaces' world"); + +// pass one parameter, hello string with spaces world +status = shell_(sbc, "scr1", "\"hello string with spaces world\""); +... +D*/ + +/* --------------------------------------------------------------- SPI API +*/ + +/*F*/ +int spi_open( + int sbc, int spi_device, int spi_channel, int spi_baud, int spi_flags); +/*D +This function 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+]. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). + spi_device: >= 0 +spi_channel: >= 0 + spi_baud: SPI speed in bits per second. + 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. +D*/ + +/*F*/ +int spi_close(int sbc, int handle); +/*D +This functions closes the SPI device identified by the handle. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +handle: >= 0 (as returned by [*spi_open*]). +. . + +If OK returns 0. + +On failure returns a negative error code. +D*/ + +/*F*/ +int spi_read(int sbc, int handle, char *buf, int count); +/*D +This function reads count bytes of data from the SPI +device + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +handle: >= 0 (as returned by [*spi_open*]). + buf: an array to receive the read data bytes. + count: the number of bytes to read. +. . + +If OK returns the count of bytes read and updates buf. + +On failure returns a negative error code. +D*/ + +/*F*/ +int spi_write(int sbc, int handle, const char *buf, int count); +/*D +This function writes count bytes of data from buf to the SPI +device + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +handle: >= 0 (as returned by [*spi_open*]). + buf: the data bytes to write. + count: the number of bytes to write. +. . + +If OK returns the count of bytes written. + +On failure returns a negative error code. +D*/ + +/*F*/ +int spi_xfer( + int sbc, int handle, const char *txBuf, char *rxBuf, int count); +/*D +This function transfers count bytes of data from txBuf to the SPI +device Simultaneously count bytes of +data are read from the device and placed in rxBuf. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +handle: >= 0 (as returned by [*spi_open*]). + txBuf: the data bytes to write. + rxBuf: the received data bytes. + count: the number of bytes to transfer. +. . + +If OK returns the count of bytes transferred and updates rxBuf. + +On failure returns a negative error code. +D*/ + + +/* ----------------------------------------------------------- THREADS API +*/ + + +/*F*/ +pthread_t *thread_start(lgThreadFunc_t thread_func, void *userdata); +/*D +Starts a new thread of execution with thread_func as the main routine. + +. . +thread_func: the main function for the new thread. + userdata: a pointer to an arbitrary argument. +. . + +If OK returns a pointer to a pthread_t. + +On failure returns NULL. + +The function is passed the single argument userdata. + +The thread can be cancelled by passing the pointer to pthread_t to +[*thread_stop*]. +D*/ + +/*F*/ +void thread_stop(pthread_t *pth); +/*D +Cancels the thread pointed at by pth. + +. . +pth: the thread to be stopped. +. . + +No value is returned. + +The thread to be stopped should have been started with [*thread_start*]. +D*/ + +/* --------------------------------------------------------- UTILITIES API +*/ + +/*F*/ +int lgu_get_internal(int sbc, int config_id, uint64_t *config_value); +/*D +Returns the value of a sbc configuration item. + +This is a privileged command. See [+permits+]. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). + config_id: the configuration item. +config_value: pointer for returned value. +. . + +If OK returns 0 and updates config_value. + +On failure returns a negative error code. +D*/ + +/*F*/ +int lgu_set_internal(int sbc, int config_id, uint64_t config_value); +/*D +Sets the value of a sbc configuration item. + +This is a privileged command. See [+permits+]. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). + config_id: the configuration item. +config_value: the value to set. +. . + +If OK returns 0. + +On failure returns a negative error code. +D*/ + +/*F*/ +int lgu_get_sbc_name(int sbc, char *buf, int count); +/*D +Return the lgd server name. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). + buf: the server name is copied to this buffer. +count: the maximum number of characters to copy. +. . + +If OK returns the count of bytes copied and updates buf. + +On failure returns a negative error code. +D*/ + +/*F*/ +int lgu_set_user(int sbc, char *user, char *secretsFile); +/*D +Sets the rgpiod daemon user. The user then has the +associated permissions. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). + user: the user to set ("" defaults to the default user). +secretsFile: the path to the shared secret file ("" defaults + to "~/.lg_secret"). +. . + +If OK returns 1 if the user was set, 0 otherwise. + +On failure returns a negative error code. + +... +if (lgu_set_user(sbc, "gpio", "") +{ + printf("using user gpio permissions"); +} +else +{ + printf("using default permissions"); +} +... +D*/ + +/*F*/ +int lgu_set_share_id(int sbc, int handle, int share_id); +/*D +Sets the share id of an owned object. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). + 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 program which created them (and are automatically +deleted when the program ends). + +If a non-zero share is set the object is accessible to any +software which knows the share and the handle (and are not +automatically deleted when the program ends). + +... +lgu_set_share_id(sbc, handle, 23); +... +D*/ + +/*F*/ +int lgu_use_share_id(int sbc, int share_id); +/*D +Sets the share id to be used when asking to use an object +owned by another creator. + +. . + sbc: >= 0 (as returned by [*rgpiod_start*]). +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 program which created them (and are automatically +deleted when the program ends). + +If a non-zero share is set the object is accessible to any +software which knows the share and the handle. + +... +lgu_use_share_id(sbc, 23); +... +D*/ + +/*F*/ +uint32_t lgu_rgpio_version(void); +/*D +Return the rgpio version. + +If OK returns the rgpio version. + +On failure returns a negative error code. +D*/ + +/*F*/ +const char *lgu_error(int errnum); +/*D +Return a text description for an error code. + +. . +errnum: the error code. +. . +D*/ + +/*F*/ +void lgu_sleep(double sleepSecs); +/*D +Delay execution for a given number of seconds. + +. . +sleepSecs: the number of seconds to delay. +. . +D*/ + +/*F*/ +double lgu_time(void); +/*D +Return the current time in seconds since the Epoch. +D*/ + +/*F*/ +uint64_t lgu_timestamp(void); +/*D +Return the current time in nanoseconds since the Epoch. +D*/ + + +/*PARAMS + +*addrStr:: +A string specifying the host or IP address of the SBC running +the rgpiod daemon. It may be NULL in which case localhost +is used unless overridden by the LG_ADDR environment +variable. + +bitVal:: +A value of 0 or 1. + +*buf:: +A buffer to hold data being sent or being received. + +byteVal::0-255 +An 8-bit byte value. + +callback_id:: +A value >= 0, as returned by a call to the [*callback*]. + +The id is passed to [*callback_cancel*] to cancel the callback. + +CBFunc_t:: +. . +typedef void (*CBFunc_t) + (int sbc, int chip, int gpio, int level, uint64_t timestamp, void * userdata); +. . + +char:: +A single character, an 8 bit quantity able to store 0-255. + +chipInfo:: +A pointer to a lgChipInfo_t object. + +config_id:: +A number identifying a configuration item. + +. . +LG_CFG_ID_DEBUG_LEVEL 0 +LG_CFG_ID_MIN_DELAY 1 +. . + +config_value:: +The value of a configuration item. + +*config_value:: +The value of a configuration item. + +count:: +The number of bytes to be transferred in a file, I2C, SPI, or serial +command. + +debounce_us:: +The debounce time in microseconds. + +double:: +A floating point number. + +edge:: +Used to identify a GPIO level transition of interest. A rising edge is +a level change from 0 to 1. A falling edge is a level change from 1 to 0. + +. . +RISING_EDGE 1 +FALLING_EDGE 2 +BOTH_EDGES 3 +. . + +eFlags:: +The type of GPIO edge to generate an alert. See [*gpio_claim_alert*]. + +. . +RISING_EDGE 1 +FALLING_EDGE 2 +BOTH_EDGES 3 +. . + + +errnum:: +A negative number indicating a function call failed and the nature +of the error. + +f:: +A function. + +*file:: +A full file path. To be accessible the path must match an entry in +the [files] section of the permits file. + +float:: +A floating point number. + +*fpat:: +A file path which may contain wildcards. To be accessible the path +must match an entry in the [files] section of the permits file. + +gpio:: +A 0 based offset of a GPIO within a gpiochip. + +gpioDev :: >= 0 +The device number of a gpiochip. + +*gpios:: +An array of GPIO numbers. + +groupBits:: +A 64-bit value used to set the levels of a GPIO group. + +Set bit x to set GPIO x of the group high. + +Clear bit x to set GPIO x of the group low. + +*groupBits:: +A 64-bit value denoting the levels of a GPIO group. + +If bit x is set then GPIO x of the group is high. + +groupMask:: +A 64-bit value used to determine which members of a GPIO 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 + + +[*file_open*] +[*gpiochip_open*] +[*i2c_open*] +[*notify_open*] +[*serial_open*] +[*script_store*] +[*spi_open*] + +i2c_addr::0-0x7F +The address of a device on the I2C bus. + +i2c_bus::>= 0 +An I2C bus number. + +i2c_flags::0 +Flags which modify an I2C open command. None are currently defined. + +i2c_reg:: 0-255 +A register of an I2C device. + +*inBuf:: +A buffer used to pass data to a function. + +inCount:: +The size of an input buffer. + +int:: +A whole number, negative or positive. + +int32_t:: +A 32-bit signed value. + +kind:: LG_TX_PWM or LG_TX_WAVE +A type of transmission: PWM or wave. + +lFlags:: +Line flags for the GPIO. + +The following values may be or'd to form the value. + +. . +LG_SET_ACTIVE_LOW +LG_SET_OPEN_DRAIN +LG_SET_OPEN_SOURCE +. . + +lgChipInfo_p:: +A pointer to a lgChipInfo_t object. + +. . +typedef struct lgChipInfo_s +{ + uint32_t lines; // number of GPIO + char name[LG_GPIO_NAME_LEN]; // Linux name + char label[LG_GPIO_LABEL_LEN]; // functional name +} lgChipInfo_t, *lgChipInfo_p; +. . + +lgLineInfo_p:: +A pointer to a lgLineInfo_t object. + +. . +typedef struct lgLine_s +{ + uint32_t offset; // GPIO number + uint32_t lFlags; + char name[LG_GPIO_NAME_LEN]; // GPIO name + char user[LG_GPIO_USER_LEN]; // user +} lgLineInfo_t, *lgLineInfo_p; +. . + + +lgPulse_p:: +A pointer to a lgPulse_t object. + +. . +typedef struct lgPulse_s +{ + uint64_t bits; + uint64_t mask; + int64_t delay; +} lgPulse_t, *lgPulse_p; +. . + +lgThreadFunc_t:: +. . +typedef void *(lgThreadFunc_t) (void *); +. . + +lineInfo:: +A pointer to a lgLineInfo_t object. + +mode:: +A file open mode. + +. . +LG_FILE_READ 1 +LG_FILE_WRITE 2 +LG_FILE_RW 3 +. . + +The following values can be or'd into the mode. + +. . +LG_FILE_APPEND 4 +LG_FILE_CREATE 8 +LG_FILE_TRUNC 16 +. . + +nfyHandle:: >= 0 +This associates a notification with a GPIO alert. + +*outBuf:: +A buffer used to return data from a function. + +outCount:: +The size of an output buffer. + +*param:: +An array of script parameters. + +*portStr:: +A string specifying the port address used by the SBC running +the rgpiod daemon. It may be NULL in which case "8889" +is used unless overridden by the LG_PORT environment +variable. + +*pth:: +A thread identifier, returned by [*thread_start*]. + +pthread_t:: +A thread identifier. + +pulse_cycles:: >= 0 +The number of pulses to generate. A value of 0 means infinite.# + +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. + +pulses:: +An pointer to an array of lgPulse_t objects. + +pulseWidth:: 0, 500-2500 microseconds +Servo pulse width + +pwmCycles:: >= 0 +The number of PWM pulses to generate. A value of 0 means infinite. + +pwmDutyCycle:: 0-100 % +PWM duty cycle % + +pwmFrequency:: 0.1-10000 Hz +PWM frequency + +pwmOffset:: >= 0 +The offset in microseconds from the nominal PWM pulse start. + +*rxBuf:: +A pointer to a buffer to receive data. + +sbc:: +An integer defining a connected SBC. The value is returned by +[*rgpiod_start*] upon success. + +*script:: +A pointer to the text of a script. + +*scriptName:: +The name of a [*shell_*] script to be executed. The script must +be present in the cgi directory of the daemon's configuration +directory and must have execute permission. + +*scriptString:: +The string to be passed to a [*shell_*] script to be executed. + +*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. + +seekFrom:: +. . +LG_FROM_START 0 +LG_FROM_CURRENT 1 +LG_FROM_END 2 +. . + +seekOffset:: +The number of bytes to move forward (positive) or backwards (negative) +from the seek position (start, current, or end of file). + +ser_baud:: +The speed of serial communication in bits per second. + +ser_flags:: +Flags which modify a serial open command. None are currently defined. + +*ser_tty:: +The name of a serial tty device, e.g. /dev/ttyAMA0, /dev/ttyUSB0, /dev/tty1. + +servoCycles:: >= 0 +The number of servo pulses to generate. A value of 0 means infinite. + +servoFrequency:: 40-500 Hz +Servo pulse frequency + +servoOffset:: >= 0 +The offset in microseconds from the nominal servo pulse start. + +share_id:: +Objects created with a non-zero share_id are persistent and may be +used by other software which knows the share_id. + +sleepSecs:: +The number of seconds to delay. + +spi_baud:: +The speed of SPI communication in bits per second. + +spi_channel:: >= 0 +A SPI channel. + +spi_device:: >= 0 +A SPI device. + +spi_flags:: +See [*spi_open*] and [*bb_spi_open*]. + +thread_func:: +A function of type gpioThreadFunc_t used as the main function of a +thread. + +*txBuf:: +An array of bytes to transmit. + +uint32_t:: +A 32-bit unsigned value. + +uint64_t:: +A 64-bit unsigned value. + +*user:: +A name known by the rgpiod daemon and associated with a set of user +permissions. + +*userdata:: +A pointer to arbitrary user data. This may be used to identify the instance. + +You must ensure that the pointer is in scope at the time it is processed. If +it is a pointer to a global this is automatic. Do not pass the address of a +local variable. If you want to pass a transient object then use the +following technique. + +In the calling function: + +. . +user_type *userdata; +user_type my_userdata; + +userdata = malloc(sizeof(user_type)); +*userdata = my_userdata; +. . + +In the receiving function: + +. . +user_type my_userdata = *(user_type*)userdata; + +free(userdata); +. . + +value:: 0-1 +A GPIO level. + +*values:: +An array of GPIO values. + +void:: +Denoting no parameter is required + +watchdog_us:: +The watchdog time in microseconds. + +wordVal::0-65535 +A 16-bit word value. + +PARAMS*/ + +/*DEF_S rgpio Error Codes*/ + +typedef enum +{ + lgif_bad_send = -2000, + lgif_bad_recv = -2001, + lgif_bad_getaddrinfo = -2002, + lgif_bad_connect = -2003, + lgif_bad_socket = -2004, + lgif_bad_noib = -2005, + lgif_duplicate_callback = -2006, + lgif_bad_malloc = -2007, + lgif_bad_callback = -2008, + lgif_notify_failed = -2009, + lgif_callback_not_found = -2010, + lgif_unconnected_sbc = -2011, + lgif_too_many_pis = -2012, +} lgifError_t; + +/*DEF_E*/ + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/rgpiod.1 b/rgpiod.1 new file mode 100644 index 0000000..f2d4a64 --- /dev/null +++ b/rgpiod.1 @@ -0,0 +1,1056 @@ + +." Process this file with +." groff -man -Tascii lgd.1 +." +.TH rgpiod 1 2020-2020 Linux "lg archive" +.SH NAME +rgpiod - a daemon to allow remote access to a SBC's GPIO. + +.SH SYNOPSIS + +rgpiod [OPTION]...& +.SH DESCRIPTION + + +.ad l + +.nh + +.br + +.br +rgpiod is a daemon to allow remote access to a SBC's GPIO. + +.br + +.br +Once launched the daemon runs in the background accepting commands from the socket interface. +.br + +.br +The daemon requires no special privileges and commands may be issued by normal users. + +.br + +.SH Features + +.br + +.br +The following features are available by issuing socket commands to the +daemon. + +.br + +.br +.br +o reading and writing GPIO singly and in groups +.br +o software timed PWM +.br +o GPIO callbacks +.br +o pipe notification of GPIO events +.br +o I2C wrapper +.br +o SPI wrapper +.br +o serial link wrapper +.br +o file handling +.br +o creating and running scripts + +.br + +.br + +.SH Launch options +rgpiod [options] & + +.br + +.br +.SH OPTIONS + +.IP "\fB-c dir \fP" +set the configuration directory (default current directory) +.br +. +.IP "\fB-l \fP" +disable remote socket interface (default enabled) +.br +. +.IP "\fB-n address \fP" +allow IP address to use the socket interface, name (e.g. paul) or dotted quad (e.g. 192.168.1.66). If the -n option is not used all addresses are allowed (unless overridden by the -l option). Multiple -n options are allowed. If -l has been used only -n localhost has any effect +.br +. +.IP "\fB-p value \fP" +set the socket port (1024-32000, default 8889) +.br +. +.IP "\fB-v \fP" +display rgpiod version and exit +.br +. +.IP "\fB-w dir \fP" +set working directory (default launch directory) +.br +. +.IP "\fB-x \fP" +enable access control (default off) +. +.SH Permissions + +.br + +.br +The rgpiod daemon has an optional access control system to control access to its functions. + +.br + +.br +See \fBPermits\fP. + +.br + +.br + +.br + +.br + +.SH Scripts + +.br + +.br +Scripts are programs to be stored and executed by the rgpiod daemon. +They are intended to mitigate any performance problems associated with +the rgpiod daemon server/client model. + +.br + +.br +See \fBScripts\fP. + +.br + +.br + +.SH Socket commands + +.br + +.br +Each socket command consists of a header and for commands with +parameters an extension. + +.br + +.br +The header is a lgCmd_t with the following structure. + +.br + +.br + +.EX +typedef struct +.br +{ +.br + union +.br + { +.br + uint32_t magic; +.br + int32_t status; +.br + }; +.br + uint32_t size; +.br + uint16_t cmd; +.br + uint16_t doubles; +.br + uint16_t longs; +.br + uint16_t shorts; +.br +} lgCmd_t, *lgCmd_p; +.br + +.EE + +.br + +.br +The magic value is 0x6c67646d (ASCII lgdm). + +.br + +.br +The size is the overall size in bytes of the optional extension. + +.br + +.br +The cmd is the command code. See the file lgSocketCommandCodes.h +for the command codes. + +.br + +.br +The doubles, longs, shorts is the number of 8-byte, 4-byte, and 2-byte +quantities in the extension. This information is used to +network order the bytes of the message. The extension should consist of +doubles 8-byte quantites, followed by longs 4-byte quantities, followed by +shorts 2-byte quantities, followed by as many 1-byte quantities needed to +make a total of size bytes. + +.br + +.br +If you wish to construct a client to talk to the rgpiod daemon the following +are a good source of information. +.br +o rpgio.py - a Python client +.br +o rgpio.c - a C client +.br +o rgs.c - a C command line client +.br +o lgCmd.c - a useful summary of the socket commands + +.SH Daemon Access Control + +.br + +.br +The rpgpio daemon operates in two modes - with or without access control. + +.br + +.br +The default setting is without access control and the permissions +system does not apply (so the rest of this section may be ignored). + +.br + +.br +If the rgpiod daemon is started with the -x option it implements +an access control permissions system to its functions. + +.br + +.br +There are three parts to the permissions system. + +.br + +.br +.br +o An .lg_secret file in the users home directory. +.br +o An .lg_secret file in the daemon's configuration directory. +.br +o A permits file in the daemon's configuration directory. + +.br + +.br +The daemon client "logs in" to the daemon by choosing a user name. If the client and daemon copies of the password for the user match the user is "logged in". + +.br + +.br +The client program is then authorised to carry out any functions permitted to the user as specified in the permits file. + +.br + +.br + +.SH User secret file + +.br + +.br +The user .lg_secret file contains a list of user names with an associated password. + +.br + +.br +These passwords have no relationship to the passwords used by Linux and should not be the same. The format is user=password. + +.br + +.br +An example .lg_secret file. + +.br + +.br + +.EX +# user secrets file +.br +# user=password +.br +pete=t4pf4kvPOXjLfDnKBrMu +.br + +.EE + +.br + +.br +The file should be readable/writable by the owner only. + +.br + +.br +chmod 600 .lg_secret + +.SH Daemon secret file + +.br + +.br +The daemon .lg_secret file contains a list of user names with an associated password. + +.br + +.br +These passwords have no relationship to the passwords used by Linux and should not be the same. The format is user=password. + +.br + +.br +An example daemon .lg_secret file. + +.br + +.br + +.EX +# rgpiod secrets file +.br +# user=password +.br +joan=kr6g89XmFQvLDWh6UcJH +.br +sally=fARrxSKqdHaPHBu6Vtet +.br +pete=t4pf4kvPOXjLfDnKBrMu +.br +fred=tugXUuRdPqGux6t7jhhv +.br + +.EE + +.br + +.br +The file should be readable/writable by the owner only. + +.br + +.br +chmod 600 .lg_secret + +.SH Daemon permits file + +.br + +.br +The permits file can contain the following sections. If a section is +absent it means that access to those features is forbidden. + +.br + +.br + +.EX +[debug] +.br +[files] +.br +[gpio] +.br +[i2c] +.br +[notify] +.br +[scripts] +.br +[serial] +.br +[shell] +.br +[spi] +.br + +.EE + +.SH [debug] +Each entry in this section takes one of the following forms: \fBuser=y\fP or \fBuser=n\fP. + +.br + +.br +If the form \fBuser=y\fP is used that user is allowed to use the debug commands. + +.br + +.br +If the form \fBuser=n\fP is used, or there is no entry for the user, that user is +not allowed to use the debug command. + +.br + +.br +If the [debug] section is not present no user is allowed to use the +debug commands. + +.br + +.br +The debug commands are set and get sbc internals and reload configuration. + +.SH [files] +Each entry in this section takes the form \fBuser=path x\fP where +\fBpath\fP indicates a file path and \fBx\fP refers to a permission. E.g. +\fB/home/peter/data.txt r\fP refers to Linux file\fB/home/peter/data.txt\fP +and read permission. + +.br + +.br +There may be more than one \fBpath\fP entry per user, each must be separated by a \fB:\fP character. + +.br + +.br +\fBpath\fP may contain the wild card characters \fB*\fP (matches any +characters) or \fB?\fP (matches a single character). + +.br + +.br +If the path entry starts with / it is relative to root (/) otherwise +it is relative to the daemons's working directory. + +.br + +.br +The permission may be R for read, W for write, U for read/write, +and N for no access. If a directory allows read/write access then +files may be created in that directory. + +.br + +.br +Where more than one entry matches a file the most specific rule +applies. If no entry matches a file then access is denied. + +.br + +.br +\fBExample\fP +.br + +.EX +joan=/tmp/* u:* n:TEST/* r:TEST/TEST/* u +.br + +.EE + +.br + +.br +User joan may create, read, and write files in the /tmp directory (\fB/tmp/* u\fP). + +.br + +.br +User joan has no access to files in the working directory (\fB* n\fP). + +.br + +.br +Overridden by user joan has read permission for files in the TEST directory +of the working directory (\fBTEST/* r\fP). + +.br + +.br +Overridden by user joan may create, read, and write files in the +TEST/TEST directory of the working directory (\fBTEST/TEST* u\fP). + +.SH [gpio] +Each entry in this section takes the form \fBuser=x.y\fP where \fBx\fP indicates +a gpiochip device and \fBy\fP indicates a GPIO. E.g. \fB1.2\fP refers to Linux device \fB/dev/gpiochip1\fP GPIO 2. + +.br + +.br +There may be more than one \fBx.y\fP entry per user, each must be separated by a \fB:\fP character. + +.br + +.br +Both x and y may have the following forms. + +.br + +.br +\fB*\fP all gpiochips or all GPIO. +.br +\fBn\fP a single gpiochip or GPIO. +.br +\fBn,n\fP a list of gpiochips or GPIO. +.br +\fBn-n\fP a range of gpiochips or GPIO. +.br + +.br + +.br +\fBExample\fP +.br + +.EX +fred=0.2-27 # user fred can access gpiochip 0 GPIO 2-27. +.br +peter=*.1,2 # user peter can access all gpiochips GPIO 1 and 2. +.br +jill=1,2.* # user jill can access all GPIO of gpiochips 1 and 2. +.br +boss=*.* # user boss can access all gpiochips and GPIO. +.br +sally=0.2-27:3.* # user sally can access gpiochip 0 GPIO 2-27 and +.br + # all GPIO of gpiochip 3. +.br + +.EE + +.br + +.br + +.SH [i2c] +Each entry in this section takes the form \fBuser=x.y\fP where \fBx\fP indicates +an I2C bus and \fBy\fP indicates a device on the bus. E.g. \fB1.27\fP refers to Linux device \fB/dev/i2c-1\fP device 27. +.br + +.br + +.br +There may be more than one \fBx.y\fP entry per user, each must be separated by a \fB:\fP character. + +.br + +.br +Both x and y may have the following forms. + +.br + +.br +\fB*\fP all I2C buses or all devices. +.br +\fBn\fP a single I2C bus or device. +.br +\fBn,n\fP a list of I2C buses or devices. +.br +\fBn-n\fP a range of I2C buses or devices. +.br + +.br + +.br +\fBExample\fP +.br + +.EX +fred=0.3-127 # user fred can access I2C bus 0 devices 3-127. +.br +peter=*.83,89 # user peter can access all I2C buses devices 83 and 89. +.br +jill=1,2.* # user jill can access all devices on I2C buses 1 and 2. +.br +boss=*.* # user boss can access all I2C buses and devices. +.br +sally=0.80-99:3.* # user sally can access I2C bus 0 devices 80-99 and +.br + # all devices of I2C bus 3. +.br + +.EE + +.br + +.br + +.SH [notify] +Each entry in this section takes one of the following forms: \fBuser=y\fP or \fBuser=n\fP. + +.br + +.br +If the form \fBuser=y\fP is used that user is allowed to use the +notify commands. + +.br + +.br +If the form \fBuser=n\fP is used, or there is no entry for the user, +that user is not allowed to use the notifiy commands. + +.br + +.br +If the [notify] section is not present no user is allowed to use the +notify commands. + +.SH [scripts] +Each entry in this section takes one of the following forms: \fBuser=y\fP or \fBuser=n\fP. + +.br + +.br +If the form \fBuser=y\fP is used that user is allowed to use the script commands. + +.br + +.br +If the form \fBuser=n\fP is used, or there is no entry for the user, that user is +not allowed to use the script command. + +.br + +.br +If the [debug] section is not present no user is allowed to use the +script commands. + +.SH [serial] +Each entry in this section takes the form \fBuser=device\fP where +\fBdevice\fP indicates a serial device. E.g. \fBserial0\fP refers to +Linux device \fB/dev/serial0\fP + +.br + +.br +There may be more than one \fBdevice\fP entry per user, each must be separated by a \fB:\fP character. + +.br + +.br +\fBdevice\fP may contain the wild card characters \fB*\fP (matches any +characters) or \fB?\fP (matches a single character). + +.br + +.br +\fBExample\fP +.br + +.EX +fred=serial0 # user fred can access /dev/serial0. +.br +peter=tty* # user peter can access /dev/tty*. +.br +boss=* # user boss can access /dev/*. +.br +sally=serial?:ttyS* # user sally can access /dev/serial? and /dev/ttyS*. +.br + +.EE + +.br + +.br + +.SH [shell] +Each entry in this section takes one of the following forms: \fBuser=y\fP or \fBuser=n\fP. + +.br + +.br +If the form \fBuser=y\fP is used that user is allowed to use the shell commands. + +.br + +.br +If the form \fBuser=n\fP is used, or there is no entry for the user, that user is +not allowed to use the shell commands. + +.br + +.br +If the [shell] section is not present no user is allowed to use the +shell commands. + +.SH [spi] +Each entry in this section takes the form \fBuser=x.y\fP where \fBx\fP +indicates a SPI bus and \fBy\fP indicates a slave select. E.g. \fB1.2\fP +refers to Linux device \fB/dev/spidev1.2\fP + +.br + +.br +There may be more than one \fBx.y\fP entry per user, each must be separated by a \fB:\fP character. + +.br + +.br +Both \fBx\fP and \fBy\fP may have the following forms. + +.br + +.br +\fB*\fP all SPI buses or all slaves. +.br +\fBn\fP a single SPI bus or slave. +.br +\fBn,n\fP a list of SPI buses or slaves. +.br +\fBn-n\fP a range of SPI buses or slaves. +.br + +.br + +.br +\fBExample\fP +.br + +.EX +fred=0.0-2 # user fred can access SPI bus 0 slaves 0-2. +.br +peter=*.0 # user peter can access all SPI buses slave 0. +.br +jill=1,2.* # user jill can access all slaves on SPI buses 1 and 2. +.br +boss=*.* # user boss can access all SPI buses and slaves. +.br +sally=0.0-2:1.* # user sally can access SPI bus 0 slaves 0-2 and +.br + # all slaves of SPI bus 1. +.br + +.EE + +.br + +.br + +.SH Example permits file + +.br + +.br + +.EX +# rgpiod test file for user access +.br +# user=permission +.br + +.br +[files] +.br +default=: +.br +test1=/tmp/* u:* n:TEST/* r:TEST/TEST/* u: +.br + +.br +[gpio] +.br +test1=*.2-27 +.br +test2=0.2-27 +.br +test3=0.5-10 +.br + +.br +[i2c] +.br +test1=1-999.* +.br +test2=1-2.* +.br +test3=2.5-20 +.br + +.br +[notify] +.br +test1=n +.br +test2=y +.br +test3=y +.br + +.br +[scripts] +.br +test1=y +.br +test2=n +.br +test3=y +.br + +.br +[serial] +.br +test1=serial*:ttyUSB*:ttyS* +.br +test2=ttyUSB1:tty0:ttyS0 +.br +test3=null +.br + +.br +[spi] +.br +test1=0.0:0.1:1.0:1.1:1.2:2.0:2.1 +.br +test2=0.* +.br +test3=*.0 +.br + +.br +[debug] +.br +admin=y +.br + +.br +[shell] +.br +test1=n +.br +test2=n +.br +test3=y +.br + +.EE + +.SH Scripts + +.br + +.br +Scripts are programs to be stored and executed by the rgpiod daemon. +They are intended to mitigate any performance problems associated with +the daemon server/client model. + +.br + +.br +Scripts are work in progress. + +.br + +.br +.SS Virtual machine +.br + +.br +A script runs within a virtual machine with + +.br + +.br +a 32 bit accumulator A. +.br +a flags register F. +.br +a program counter PC. + +.br + +.br +Each script has + +.br + +.br +10 parameters named 0 through 9. +.br +150 variables named 0 through 149. +.br +50 labels which are named by any unique number. + +.br + +.br +.SS Commands +.br + +.br +Many lg commands may be used within a script. However +some commands do not work within the script model as designed and +are not permitted. + +.br + +.br +The following commands are not permitted within a script: +.br + +.br + +.br +File - FL FO FR FW + +.br + +.br +I2C - I2CPK I2CRD I2CRI I2CRK I2CWD I2CWI I2CWK I2CZ + +.br + +.br +Script control - PARSE PROC PROCD PROCP PROCR PROCS PROCU + +.br + +.br +Serial - SERO SERR SERW SLR + +.br + +.br +SPI - SPIR SPIW SPIX + +.br + +.br +The following commands are only permitted within a script: +.br + +.br + +.br +Command Description Definition +.br +ADD x Add x to accumulator A+=x; F=A +.br +AND x And x with accumulator A&=x; F=A +.br +CALL L Call subroutine at tag L push(PC+1); PC=L +.br +CMP x Compare x with accumulator F=A-x +.br +DCR y Decrement register --*y; F=*y +.br +DCRA Decrement accumulator --A; F=A +.br +DIV x Divide x into accumulator A/=x; F=A +.br +HALT Halt Halt +.br +INR y Increment register ++*y; F=*y +.br +INRA Increment accumulator ++A; F=A +.br +JGE L Jump if >= 0 to tag L if (F>=0) PC=L +.br +JGT L Jump if > 0 to tag L if (F>0) PC=L +.br +JLE L Jump if <= 0 to tag L if (F<=0) PC=L +.br +JLT L Jump if < 0 to tag L if (F<0) PC=L +.br +JMP L Jump to tag L PC=L +.br +JNZ L Jump if non-zero to tag L if (F) PC=L +.br +JZ L Jump if zero to tag L if (!F) PC=L +.br +LD y x Load register with x *y=x +.br +LDA x Load accumulator with x A=x +.br +MLT x Multiply x with accumulator A*=x; F=A +.br +MOD x Modulus x with accumulator A%=x; F=A +.br +OR x Or x with accumulator A|=x; F=A +.br +POP y Pop register y=pop() +.br +POPA Pop accumulator A=pop() +.br +PUSH y Push register push(y) +.br +PUSHA Push accumulator push(A) +.br +RET Return from subroutine PC=pop() +.br +RL y x Rotate left register x bits *y<<=x; F=*y +.br +RLA x Rotate left accumulator x bits A<<=x; F=A +.br +RR y x Rotate right register x bits *y>>=x; F=*y +.br +RRA x Rotate right accumulator x bits A>>=x; F=A +.br +SHL y x Shift left register x bits *y<<=x; F=*y +.br +SHLA x Shift left accumulator x bits A<<=x; F=A +.br +SHR y x Shift right register x bits *y>>=x; F=*y +.br +SHRA x Shift right accumulator x bits A>>=x; F=A +.br +STA y Store accumulator in register y=A +.br +SUB x Subtract x from accumulator A-=x; F=A +.br +SYS str Run external script system(str); F=A +.br +TAG L Label the current position N/A +.br +X y1 y2 Exchange registers y1 and y2 t=*y1;*y1=*y2;*y2=t +.br +XA y Exchange accumulator and register t=A;A=*y;*y=t +.br +XOR x Xor x with accumulator A^=x; F=A +.br + +.br + +.br +x may be a constant, a parameter (p0-p9), or a variable (v0-v149). + +.br + +.br +y may be a parameter (p0-p9), or a variable (v0-v149). If p or v isn't +specified y is assumed to be a variable. + +.br + +.br +The SYS script receives two unsigned parameters: the accumulator A and +the current GPIO levels. + +.br + +.br + +.SH SEE ALSO + +rgs(1), lgpio(3), rgpio(3) diff --git a/rgpiod.c b/rgpiod.c new file mode 100644 index 0000000..00800f2 --- /dev/null +++ b/rgpiod.c @@ -0,0 +1,303 @@ +/* +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 +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lgpio.h" +#include "rgpiod.h" + +#include "lgDbg.h" + +/* globals */ + +int gPermits = 0; +int gNumSockNetAddr = 0; +uint32_t gSockNetAddr[MAX_CONNECT_ADDRESSES]; +int gFdSock = -1; + +/* locals */ + +static int CfgIfFlags = LG_DEFAULT_IF_FLAGS; +static int CfgSocketPort = LG_DEFAULT_SOCKET_PORT; +static pthread_t pthSocket; + +/* prototypes */ + +void *pthSocketThread(void *x); + +/* ----------------------------------------------------------------------- */ + +static int xOpenSocket(void) +{ + int i; + int opt=1; + struct sockaddr_in server; + struct sockaddr_in6 server6; + char *portStr; + int port; + pthread_attr_t pthAttr; + + LG_DBG(LG_DEBUG_STARTUP, ""); + + gFdSock = -1; + + if (pthread_attr_init(&pthAttr)) + PARAM_ERROR(LG_INIT_FAILED, "pthread_attr_init failed (%m)"); + + if (pthread_attr_setstacksize(&pthAttr, STACK_SIZE)) + PARAM_ERROR(LG_INIT_FAILED, "pthread_attr_setstacksize failed (%m)"); + + portStr = getenv(LG_ENVPORT); + if (portStr) port = atoi(portStr); else port = CfgSocketPort; + + // Accept connections on IPv6, unless we have an IPv4-only whitelist + if (!gNumSockNetAddr) + { + gFdSock = socket(AF_INET6, SOCK_STREAM , 0); + + if (gFdSock != -1) + { + bzero((char *)&server6, sizeof(server6)); + server6.sin6_family = AF_INET6; + if (CfgIfFlags & LG_LOCALHOST_SOCK_IF) + { + server6.sin6_addr = in6addr_loopback; + } + else + { + server6.sin6_addr = in6addr_any; + } + server6.sin6_port = htons(port); + + setsockopt(gFdSock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + if (bind(gFdSock,(struct sockaddr *)&server6, sizeof(server6)) < 0) + PARAM_ERROR(LG_INIT_FAILED, "bind to port %d failed (%m)", port); + } + } + + if (gNumSockNetAddr || gFdSock == -1) + { + gFdSock = socket(AF_INET , SOCK_STREAM , 0); + + if (gFdSock == -1) + PARAM_ERROR(LG_INIT_FAILED, "socket failed (%m)"); + else + { + setsockopt(gFdSock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + } + server.sin_family = AF_INET; + if (CfgIfFlags & LG_LOCALHOST_SOCK_IF) + { + server.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + } + else + { + server.sin_addr.s_addr = htonl(INADDR_ANY); + } + server.sin_port = htons(port); + + if (bind(gFdSock,(struct sockaddr *)&server , sizeof(server)) < 0) + PARAM_ERROR(LG_INIT_FAILED, "bind to port %d failed (%m)", port); + } + + if (pthread_create(&pthSocket, &pthAttr, pthSocketThread, &i)) + PARAM_ERROR(LG_INIT_FAILED, "pthread_create socket failed (%m)"); + + return LG_OKAY; +} + +static void xFatal(char *fmt, ...) +{ + char buf[128]; + va_list ap; + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + + fprintf(stderr, "%s\n", buf); + + fflush(stderr); + + exit(EXIT_FAILURE); +} + +static void xUsage() +{ + fprintf(stderr, "\n" \ + "Usage: rgpiod [OPTION] ...\n" \ + " -c dir, set config dir (default launch dir)\n" \ + " -l, localhost socket only (default local+remote)\n" \ + " -n IP addr, allow address, name or dotted (default allow all)\n" \ + " -p value, socket port (1024-32000, default 8889)\n" \ + " -v, display rgpiod version and exit\n" \ + " -w dir, set working directory (default launch directory)\n" \ + " -x, enable access control (default off)\n" \ + "EXAMPLE\n" \ + "rgpiod -p 9000 &\n" \ + " Start with socket port 9000\n" \ + "\n"); +} + +static uint64_t xGetNum(char *str, int *err) +{ + uint64_t val; + char *endptr; + + *err = 0; + val = strtoll(str, &endptr, 0); + if (*endptr) {*err = 1; val = -1;} + return val; +} + +static uint32_t xCheckAddr(char *addrStr) +{ + int err; + struct addrinfo hints, *res; + struct sockaddr_in *sin; + const char *portStr; + uint32_t addr; + + portStr = getenv(LG_ENVPORT); + + if (!portStr) portStr = LG_DEFAULT_SOCKET_PORT_STR; + + memset (&hints, 0, sizeof (hints)); + + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags |= AI_CANONNAME; + + err = getaddrinfo(addrStr, portStr, &hints, &res); + + if (err) return 0; + + sin = (struct sockaddr_in *)res->ai_addr; + addr = sin->sin_addr.s_addr; + + freeaddrinfo(res); + + return addr; +} + +static void xInitOpts(int argc, char *argv[]) +{ + int opt, err, i; + uint32_t addr; + + while ((opt = getopt(argc, argv, "c:ln:p:vw:x")) != -1) + { + switch (opt) + { + case 'c': /* configuration directory */ + lguSetConfigDir(optarg); + break; + + case 'l': + CfgIfFlags |= LG_LOCALHOST_SOCK_IF; + break; + + case 'n': + addr = xCheckAddr(optarg); + if (addr && (gNumSockNetAddr= LG_MIN_SOCKET_PORT) && (i <= LG_MAX_SOCKET_PORT)) + CfgSocketPort = i; + else xFatal("invalid -p option (%d)", i); + break; + + case 'v': + printf("rgpiod_%d.%d.%d.%d\n", + (RGPIOD_VERSION>>24)&0xff, (RGPIOD_VERSION>>16)&0xff, + (RGPIOD_VERSION>>8)&0xff, RGPIOD_VERSION&0xff); + exit(EXIT_SUCCESS); + break; + + case 'w': /* working directory */ + lguSetWorkDir(optarg); + break; + + case 'x': /* enable user permissions checks */ + gPermits = 1; + break; + + default: /* '?' */ + xUsage(); + exit(EXIT_FAILURE); + } + } +} + +int main(int argc, char **argv) +{ + int flags; + + /* check command line parameters */ + + xInitOpts(argc, argv); + + /* initialise */ + + if (xOpenSocket() >= 0) + { + /* set stderr non-blocking */ + + flags = fcntl(fileno(stderr), F_GETFL, 0); + fcntl(fileno(stderr), F_SETFL, flags | O_NONBLOCK); + + /* sleep forever */ + + while (1) + { + sleep(1); + + fflush(stderr); + } + } + + return -1; +} + diff --git a/rgpiod.h b/rgpiod.h new file mode 100644 index 0000000..1ecf53b --- /dev/null +++ b/rgpiod.h @@ -0,0 +1,283 @@ +/* +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 +*/ + +#ifndef RGPIOD_H +#define RGPIOD_H + +#include + +#include "lgCmd.h" + +#define RGPIOD_VERSION 0x00000000 + +#define LG_ENVPORT "LG_PORT" +#define LG_ENVADDR "LG_ADDR" + +#define LG_DEFAULT_USER "default" + +#define LG_DEFAULT_IF_FLAGS 0 +#define LG_DEFAULT_SOCKET_PORT 8889 +#define LG_DEFAULT_SOCKET_PORT_STR "8889" +#define LG_DEFAULT_SOCKET_ADDR_STR "localhost" + +#ifdef __cplusplus +extern "C" { +#endif + +/* prototypes +*/ + +int lgExecCmd(lgCmd_p h, int bufSize); + +/* port */ + +#define LG_MIN_SOCKET_PORT 1024 +#define LG_MAX_SOCKET_PORT 32000 + +/* ifFlags: */ + +#define LG_LOCALHOST_SOCK_IF 4 + +/* Allowed socket connect addresses */ + +#define MAX_CONNECT_ADDRESSES 256 + +/* File API +*/ + +int lgFileOpen(char *file, int mode); +int lgFileClose(int handle); +int lgFileRead(int handle, char *buf, int count); +int lgFileWrite(int handle, char *buf, int count); +int lgFileSeek(int handle, int32_t seekOffset, int seekFrom); +int lgFileList(char *fpat, char *buf, int count); + +/* Script API +*/ + +int lgScriptStore(char *script); +int lgScriptRun(int handle, int count, uint32_t *scriptParam); +int lgScriptUpdate(int handle, int count, uint32_t *scriptParam); +int lgScriptStatus(int handle, uint32_t *scriptParam); +int lgScriptStop(int handle); +int lgScriptDelete(int handle); + +int lgShell(char *scriptName, char *scriptString); + +/* globals +*/ + +extern int gPermits; +extern int gNumSockNetAddr; +extern uint32_t gSockNetAddr[MAX_CONNECT_ADDRESSES]; +extern int gFdSock; + +#ifdef __cplusplus +} +#endif + +/*DEF_S Socket Command Codes*/ + +#define LG_CMD_FO 1 // file open +#define LG_CMD_FC 2 // file close +#define LG_CMD_FR 3 // file read +#define LG_CMD_FW 4 // file write +#define LG_CMD_FS 5 // file seek +#define LG_CMD_FL 6 // file list + +#define LG_CMD_GO 10 // gpiochip open +#define LG_CMD_GC 11 // gpiochip close + +#define LG_CMD_GSIX 12 // gpio claim for input +#define LG_CMD_GSOX 13 // gpio claim for output +#define LG_CMD_GSAX 14 // gpio claim for alerts +#define LG_CMD_GSF 15 // gpio free + +#define LG_CMD_GSGIX 16 // gpio group claim for input +#define LG_CMD_GSGOX 17 // gpio group claim for output +#define LG_CMD_GSGF 18 // gpio group free + +#define LG_CMD_GR 19 // gpio read +#define LG_CMD_GW 20 // gpio write +#define LG_CMD_GGR 21 // gpio group read +#define LG_CMD_GGWX 22 // gpio group write + +#define LG_CMD_GPX 23 // gpio software timed pulses +#define LG_CMD_PX 24 // gpio software timed PWM +#define LG_CMD_SX 25 // gpio software timed servo pulses +#define LG_CMD_GWAVE 26 // gpio software timed waves +#define LG_CMD_GBUSY 27 // tx busy +#define LG_CMD_GROOM 28 // tx room +#define LG_CMD_GDEB 29 // gpio set debounce time +#define LG_CMD_GWDOG 30 // gpio set watchdog time + +#define LG_CMD_GIC 31 // gpiochip get chip info +#define LG_CMD_GIL 32 // gpiochip get line info +#define LG_CMD_GMODE 33 // gpio get mode + +#define LG_CMD_I2CO 40 // I2C open +#define LG_CMD_I2CC 41 // I2C close +#define LG_CMD_I2CRD 42 // I2C read device +#define LG_CMD_I2CWD 43 // I2C write device +#define LG_CMD_I2CWQ 44 // SMBus Write Quick +#define LG_CMD_I2CRS 45 // SMBus Read Byte +#define LG_CMD_I2CWS 46 // SMBus Write Byte +#define LG_CMD_I2CRB 47 // SMBus Read Byte Data +#define LG_CMD_I2CWB 48 // SMBus Write Byte Data +#define LG_CMD_I2CRW 49 // SMBus Read Word +#define LG_CMD_I2CWW 50 // SMBus Write Word +#define LG_CMD_I2CRK 51 // SMBus Read Block Data +#define LG_CMD_I2CWK 52 // SMBus Write Block Data +#define LG_CMD_I2CRI 53 // SMBus Read I2C Block Data +#define LG_CMD_I2CWI 54 // SMBus Write I2C Block Data +#define LG_CMD_I2CPC 55 // SMBus Process Call +#define LG_CMD_I2CPK 56 // SMBus Block Process Call +#define LG_CMD_I2CZ 57 // I2C zip (multiple commands) + +#define LG_CMD_NO 70 // notification open +#define LG_CMD_NC 71 // notification close +#define LG_CMD_NR 72 // notification resume +#define LG_CMD_NP 73 // notification pause + +#define LG_CMD_PARSE 80 // script parse +#define LG_CMD_PROC 81 // script store +#define LG_CMD_PROCD 82 // script delete +#define LG_CMD_PROCP 83 // script status +#define LG_CMD_PROCR 84 // script run +#define LG_CMD_PROCS 85 // script stop +#define LG_CMD_PROCU 86 // script update parameters + +#define LG_CMD_SERO 90 // serial open +#define LG_CMD_SERC 91 // serial close +#define LG_CMD_SERRB 92 // serial read byte +#define LG_CMD_SERWB 93 // serial write byte +#define LG_CMD_SERR 94 // serial read bytes +#define LG_CMD_SERW 95 // serial write bytes +#define LG_CMD_SERDA 96 // serial data available + +#define LG_CMD_SPIO 100 // SPI open +#define LG_CMD_SPIC 101 // SPI close +#define LG_CMD_SPIR 102 // SPI read bytes +#define LG_CMD_SPIW 103 // SPI write bytes +#define LG_CMD_SPIX 104 // SPI transfer bytes + +#define LG_CMD_MICS 113 // delay for a number of microseconds +#define LG_CMD_MILS 114 // delay for a number of milliseconds +#define LG_CMD_CGI 115 // get internals setting +#define LG_CMD_CSI 116 // set internals setting +#define LG_CMD_NOIB 117 // open a notification inband in a socket +#define LG_CMD_SHELL 118 // run a shell command + +#define LG_CMD_SBC 120 // print the SBC's host name +#define LG_CMD_FREE 121 // release resources + +#define LG_CMD_SHARE 130 // set the share id for handles +#define LG_CMD_USER 131 // set the user +#define LG_CMD_PASSW 132 // submit the password +#define LG_CMD_LCFG 133 // reload the permits file +#define LG_CMD_SHRU 134 // use this share to access handles +#define LG_CMD_SHRS 135 // set this share on created handles +#define LG_CMD_PWD 136 // print the daemon working directory +#define LG_CMD_PCD 137 // print the daemon configuration directory + +#define LG_CMD_LGV 140 // print the lg library version +#define LG_CMD_TICK 141 // print the number of nanonseconds since the epoch + +/*DEF_E*/ + +/*DEF_S Convenience Command Codes*/ + +#define LG_CMD_GGW 600 // simple GPIO group write +#define LG_CMD_GP 601 // simple GPIO tx pulses +#define LG_CMD_GSA 602 // simple GPIO claim for alerts +#define LG_CMD_GSGI 603 // simple GPIO group claim for inputs +#define LG_CMD_GSGO 604 // simple GPIO group claim for outputs +#define LG_CMD_GSI 605 // simple GPIO claim for input +#define LG_CMD_GSO 606 // simple GPIO claim for output +#define LG_CMD_P 607 // simple GPIO tx PWM +#define LG_CMD_S 608 // simple GPIO tx servo pulses + +/*DEF_E*/ + +/*DEF_S Script Command Codes*/ + +#define LG_CMD_SCRIPT 800 + +#define LG_CMD_ADD 800 +#define LG_CMD_AND 801 +#define LG_CMD_CALL 802 +#define LG_CMD_CMDR 803 +#define LG_CMD_CMDW 804 +#define LG_CMD_CMP 805 +#define LG_CMD_DCR 806 +#define LG_CMD_DCRA 807 +#define LG_CMD_DIV 808 +#define LG_CMD_HALT 809 +#define LG_CMD_INR 810 +#define LG_CMD_INRA 811 +#define LG_CMD_JGE 812 +#define LG_CMD_JGT 813 +#define LG_CMD_JLE 814 +#define LG_CMD_JLT 815 +#define LG_CMD_JMP 816 +#define LG_CMD_JNZ 817 +#define LG_CMD_JZ 818 +#define LG_CMD_TAG 819 +#define LG_CMD_LD 820 +#define LG_CMD_LDA 821 +#define LG_CMD_LDAB 822 +#define LG_CMD_MLT 823 +#define LG_CMD_MOD 824 +#define LG_CMD_NOP 825 +#define LG_CMD_OR 826 +#define LG_CMD_POP 827 +#define LG_CMD_POPA 828 +#define LG_CMD_PUSH 829 +#define LG_CMD_PUSHA 830 +#define LG_CMD_RET 831 +#define LG_CMD_RL 832 +#define LG_CMD_RLA 833 +#define LG_CMD_RR 834 +#define LG_CMD_RRA 835 +#define LG_CMD_SHL 836 +#define LG_CMD_SHLA 837 +#define LG_CMD_SHR 838 +#define LG_CMD_SHRA 839 +#define LG_CMD_STA 840 +#define LG_CMD_STAB 841 +#define LG_CMD_SUB 842 +#define LG_CMD_SYS 843 +#define LG_CMD_WAIT 844 +#define LG_CMD_X 845 +#define LG_CMD_XA 846 +#define LG_CMD_XOR 847 +#define LG_CMD_EVTWT 848 + +/*DEF_E*/ + +#endif + diff --git a/rgs.1 b/rgs.1 new file mode 100644 index 0000000..7c5aac0 --- /dev/null +++ b/rgs.1 @@ -0,0 +1,3712 @@ + +." Process this file with +." groff -man -Tascii foo.1 +." +.TH rgs 1 2020-2020 Linux "lg archive" +.SH NAME +rgs - a shell command to manipulate a remote SBC's GPIO. + +.SH SYNOPSIS + +.B rgpiod & + +then + +.B rgs {command}+ + +.SH DESCRIPTION + +.ad l + +.nh + + +.br +rgs is a program which allows remote control of the GPIO and +other functions of Linux SBCs running the rgpiod daemon. + +.br +The rgpiod daemon must be running on the SBCs you wish to control. + +.br +.SS Features +.br + +.br +o reading and writing GPIO singly and in groups + +.br +o software timed PWM and waves + +.br +o GPIO callbacks + +.br +o pipe notification of GPIO events + +.br +o I2C wrapper + +.br +o SPI wrapper + +.br +o serial link wrapper + +.br +o simple file handling + +.br +o creating and running scripts on the rgpiod daemon + +.br +.SS Usage +.br +rgs {command}+ + +.br +rgs will show the result of the command on screen. + +.br +The rgs process returns an exit status (which can be displayed with +the command echo $?). + +.br + +.EX +RGS_OK 0 +.br +RGS_CONNECT_ERR 255 +.br +RGS_OPTION_ERR 254 +.br +RGS_SCRIPT_ERR 253 +.br +.br +.br + +.EE + +.br +If an error was detected a message will have been written to stderr. +This is likely to be more informative than the message returned by rgs. + +.br +Several commands may be entered on a line. If present PROC and PARSE must +be the last command on a line. + +.br +.SS Notes +.br +rgs does not show the status of successful commands unless the +command itself returns data. The status (0) will be returned to +rgs but will be discarded. + +.br +When a command takes a number as a parameter it may be entered as hex +(precede by 0x), octal (precede by 0), or decimal. + +.br +E.g. 23 is 23 decimal, 0x100 is 256 decimal, 070 is 56 decimal. + +.br +Some commands can return a variable number of data bytes. By +default this data is displayed as decimal. The rgs -a option +can be used to force the display as ASCII and the rgs -x +option can be used to force the display as hex. + +.br +E.g. assuming the transmitted serial data is the letters ABCDEONM + +.br + +.EX +$ rgs serr 4 100 # assumes serial data available from handle 4 +.br +8 65 66 67 68 69 79 78 77 +.br + +.br +$ rgs -a serr 4 100 +.br +8 ABCDEONM +.br + +.br +$ rgs -x serr 4 100 +.br +8 41 42 43 44 45 4f 4e 4d +.br + +.EE + +.br +.SS Permissions +.br +Generally objects created on the rgpiod daemon exist for the duration of the +socket connection. + +.br +For a Python script this will be for the duration of the script. For a +program linked with rgpio this will be for the duration of the program. + +.br +For rgs it is the command line. + +.br +This means that the following command will achieve little + +.br + +.EX +rgs go 0 # get handle to gpiochip 0 +.br + +.EE + +.br +The daemon will delete the handle as soon as the rgs command has finished. + +.br +To preserve the handle it must be shared. + +.br +A lot of the examples will show the command c 1 (use share id 1). +This means the handle is preserved and may be used in subsequent commands. + +.br + +.EX +rgs c 1 go 0 # get and preserve handle to gpiochip 0 +.br + +.EE + +.br +If a command is privileged it is indicated in the notes for the +command. The examples given here assume the daemon access control +system is not active (so any user can use privileged commands). + +.br + +.SH OVERVIEW +.SS FILES +.B FO file mode +File open +.P +.B FC h +File close +.P +.B FR h num +File read +.P +.B FW h bvs +File write +.P +.B FS h num from +File seek +.P +.B FL pat num +File list +.P +.SS GPIO +.br +.B GO gc +gpiochip open device +.P +.B GC h +gpiochip close device +.P +.B GIC h +gpiochip information +.P +.B GIL h g +gpiochip line information +.P +.B GMODE h g +GPIO get mode +.P +.B GSI h g +GPIO claim for input (simple) +.P +.B GSIX h lf g +GPIO claim for input +.P +.B GSO h g +GPIO claim for output (simple) +.P +.B GSOX h lf g v +GPIO claim for output +.P +.B GSA h g nfyh +GPIO claim for alerts (simple) +.P +.B GSAX h lf ef g nfyh +GPIO claim for alerts +.P +.B GSF h g +GPIO free +.P +.B GSGI h g* +GPIO group claim for inputs (simple) +.P +.B GSGIX h lf g* +GPIO group claim for inputs +.P +.B GSGO h g* +GPIO group claim for outputs (simple) +.P +.B GSGOX h lf g* v* +GPIO group claim for outputs +.P +.B GSGF h g +GPIO group free +.P +.B GR h g +GPIO read +.P +.B GW h g v +GPIO write +.P +.B GGR h g +GPIO group read +.P +.B GGW h g gbits +GPIO group write (simple) +.P +.B GGWX h g gbits gmask +GPIO group write +.P +.B GP h g mon moff +GPIO tx pulse (simple) +.P +.B GPX h g mon moff off cyc +GPIO tx pulse +.P +.B P h g pf pdc +GPIO tx PWM (simple) +.P +.B PX h g pf pdc off cyc +GPIO tx PWM +.P +.B S h g spw +GPIO tx servo pulses (simple) +.P +.B SX h g spw sf off cyc +GPIO tx servo pulses +.P +.B GWAVE h g p* +GPIO group tx wave +.P +.B GBUSY h g k +GPIO or group tx busy +.br +.P +.B GROOM h g k +GPIO or group tx entries +.P +.B GDEB h g us +GPIO debounce time +.P +.B GWDOG h g us +GPIO watchdog time +.P +.SS I2C +.B I2CO ib id if +I2C open device +.P +.B I2CC h +I2C close device +.P +.B I2CWQ h bit +SMB Write Quick: write bit +.P +.B I2CRS h +SMB Read Byte: read byte +.P +.B I2CWS h bv +SMB Write Byte: write byte +.P +.B I2CRB h r +SMB Read Byte Data: read byte from register +.P +.B I2CWB h r bv +SMB Write Byte Data: write byte to register +.P +.B I2CRW h r +SMB Read Word Data: read word from register +.P +.B I2CWW h r wv +SMB Write Word Data: write word to register +.P +.B I2CRK h r +SMB Read Block Data: read data from register +.P +.B I2CWK h r bvs +SMB Write Block Data: write data to register +.P +.B I2CWI h r bvs +SMB Write I2C Block Data +.P +.B I2CRI h r num +SMB Read I2C Block Data: read bytes from register +.P +.B I2CRD h num +I2C read device +.P +.B I2CWD h bvs +I2C write device +.P +.B I2CPC h r wv +SMB Process Call: exchange register with word +.P +.B I2CPK h r bvs +SMB Block Process Call: exchange data bytes with register +.P +.B I2CZ h bvs +I2C zip +.P +.SS NOTIFICATIONS +.B NO +Notification open +.P +.B NC h +Notification close +.P +.B NP h +Notification pause +.P +.B NR h +Notification resume +.P +.SS SCRIPTS +.B PROC t +Script store +.P +.B PROCR h pars +Script run +.P +.B PROCU h pars +Script update parameters +.P +.B PROCP h +Script get status and parameters +.P +.B PROCS h +Script stop +.P +.B PROCD h +Script delete +.P +.B PARSE t +Script validate +.P +.SS SERIAL +.B SERO dev b sef +Serial open device +.P +.B SERC h +Serial close device +.P +.B SERRB +Serial read byte +.P +.B SERWB h bv +Serial write byte +.P +.B SERR h num +Serial read bytes +.P +.B SERW h bvs +Serial write bytes +.P +.B SERDA h +Serial data available +.P +.SS SHELL +.B SHELL name str +Execute a shell command +.P +.SS SPI +.B SPIO spd spc b spf +SPI open device +.P +.B SPIC h +SPI close device +.P +.B SPIR h num +SPI read bytes +.P +.B SPIW h bvs +SPI write bytes +.P +.B SPIX h bvs +SPI transfer bytes +.P +.SS UTILITIES +.B LGV +Get lg library version +.P +.B SBC +Get SBC's host name +.P +.B CGI cid +Get internal configuration setting +.P +.B CSI cid v +Set internal configuration setting +.P +.B T/TICK +Get nanoseconds since the epoch +.P +.B MICS v +Microseconds delay +.P +.B MILS v +Milliseconds delay +.P +.B U/USER +Set user +.P +.B C/SHARE +Set share +.P +.B LCFG +Reload permits configuration file +.P +.B PCD +Print daemon configuration directory +.P +.B PWD +Print daemon working directory +.P + +.SH COMMANDS + +.br +.SS FILES +.br + +.IP "\fBFO file mode\fP - File open" +.IP "" 4 +This is a privileged command. See \fBpermits\fP. + +.br +This function returns a handle to a file opened in a specified mode. + +.br +Upon success a handle (>=0) is returned. On error a negative status code +will be returned. + +.br +The mode may have the following values. + +.br + +.EX + Value Meaning +READ 1 open file for reading +WRITE 2 open file for writing +RW 3 open file for reading and writing + +.EE + +.br +The following values may be or'd into the mode. + +.br + +.EX + Value Meaning +APPEND 4 All writes append data to the end of the file +CREATE 8 The file is created if it doesn't exist +TRUNC 16 The file is truncated + +.EE + +.br +Newly created files are owned by the user that launched the daemon +with permissions owner read and write. + +.br + +\fBExample\fP +.br + +.EX +ls /ram/*.c +.br +/ram/q.c /ram/qdhtxx.c /ram/q-errcod.c /ram/q_t1.c +.br +/ram/q-c1.c /ram/Q-err.c /ram/q-group.c /ram/q_t2.c +.br + +.br +$ rgs c 1 fo /ram/q.c 1 # read access +.br +1 +.br + +.br +$ rgs c 1 fo /ram/new.c 1 # file does not exist +.br +-58 +.br +ERROR: file open failed +.br + +.br +$rgs c 1 fo /ram/new.c 9 # can not create file +.br +-67 +.br +ERROR: no permission to access file +.br + +.EE + +.br + +.IP "\fBFC h\fP - File close" +.IP "" 4 +This command closes a file previously opened by \fBFO\fP. + +.br +Upon success nothing is returned. On error a negative status code +will be returned. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 fc 1 # First close okay. +.br + +.br +$ rgs c 1 fc 1 # Second fails. +.br +-5 +.br +ERROR: unknown handle +.br + +.EE + +.br + +.br + +.IP "\fBFR h num\fP - File read" +.IP "" 4 +This command returns up to \fBnum\fP bytes of data read from the file. + +.br +Upon success the count of returned bytes followed by the bytes themselves +is returned. On error a negative status code will be returned. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 fr 0 10 +.br +5 48 49 128 144 255 +.br + +.br +$ rgs c 1 fr 0 10 +.br +0 +.br + +.EE + +.br + +.IP "\fBFW h bvs\fP - File write" +.IP "" 4 +This command writes \fBbvs\fP bytes to the file. + +.br +Upon success nothing is returned. On error a negative status code +will be returned. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 fw 0 23 45 67 89 +.br + +.EE + +.br + +.IP "\fBFS h num from\fP - File seek" +.IP "" 4 +This command seeks to a position within the file. + +.br +The number of bytes to move is \fBnum\fP. Positive offsets +move forward, negative offsets backwards. The move start +position is determined by \fBfrom\fP as follows. + +.br + +.EX + From +0 start +1 current position +2 end + +.EE + +.br +Upon success the new byte position within the file (>=0) is +returned. On error a negative status code will be returned. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 fs 0 200 0 # Seek to start of file plus 200 +.br +200 +.br + +.br +$ rgs c 1 fs 0 0 1 # Return current position +.br +200 +.br + +.br +$ rgs c 1 fs 0 0 2 # Seek to end of file, return size +.br +296235 +.br + +.EE + +.br + +.IP "\fBFL pat num\fP - File list" +.IP "" 4 +This command returns a list of the files matching \fBpat\fP. Up +to \fBnum\fP bytes may be returned. + +.br +Upon success the count of returned bytes followed by the matching +files is returned. On error a negative status code will be returned. + +.br +A newline (0x0a) character separates each file name. + +.br +This is a privileged command. See \fBpermits\fP. + +.br + +\fBExample\fP +.br + +.EX +$ rgs -a fl "/sys/bus/w1/devices/28*/w1_slave" 5000 +.br +90 /sys/bus/w1/devices/28-000005d34cd2/w1_slave +.br +/sys/bus/w1/devices/28-001414abbeff/w1_slave +.br + +.br +$ rgs -a fl "/sys/bus/*" 5000 +.br +ERROR: no permission to access file +.br +-67 +.br + +.EE + +.br +.SS GPIO +.br + +.IP "\fBGO gc\fP - gpiochip open device" +.IP "" 4 + +.br +This is a privileged command. See \fBpermits\fP. + +.br +This command opens a gpiochip. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 go 0 # open /dev/gpiochip0 +.br +1 +.br +$ rgs c 1 go 23 # try to open /dev/gpiochip23 +.br +-78 +.br +ERROR: can not open gpiochip +.br + +.EE + +.br + +.IP "\fBGC h\fP - gpiochip close device" +.IP "" 4 + +.br +This command closes a gpiochip previously opened by \fBGO\fP. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 gc 1 # first close ok +.br +$ rgs c 1 gc 1 # already closed +.br +-5 +.br +ERROR: unknown handle +.br + +.EE + +.br + +.IP "\fBGIC h\fP - gpiochip information" +.IP "" 4 + +.br +This command gets information for an opened gpiochip. In particular +it gets the number of GPIO on the gpiochip, its name, and its usage. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 gic 1 +.br +54 "gpiochip0" "pinctrl-bcm2835" +.br + +.EE + +.br + +.IP "\fBGIL h g\fP - gpiochip line information" +.IP "" 4 + +.br +This command gets information for GPIO \fBg\fP of an opened gpiochip. +In particular it gets the GPIO number, kernel usage flags, its user, +and its purpose. + +.br +The usage flags are bits. + +.br + +.EX +Bit value Bit meaning +1 GPIO in use by the kernel +2 GPIO is an output +4 GPIO is active low +8 GPIO is open drain +16 GPIO is open source + +.EE + +.br +The user and purpose fields are filled in by the software which has +claimed the GPIO and may be blank. + +.br + +\fBExample\fP +.br + +.EX +$ for ((i=2; i<10; i++)); do rgs c 1 gil 1 $i; done +.br +2 0 "" "" +.br +3 0 "" "" +.br +4 11 "" "onewire.0" +.br +5 0 "" "" +.br +6 0 "" "" +.br +7 7 "" "spi0 CS1" +.br +8 7 "" "spi0 CS0" +.br +9 0 "" "" +.br + +.EE + +.br + +.IP "\fBGMODE h g\fP - GPIO get mode" +.IP "" 4 + +.br +This command gets the mode for GPIO \fBg\fP of an opened gpiochip. + +.br + +.EX +Mode 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: --- +6 64 Kernel: --- +7 128 Kernel: --- +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: --- + +.EE + +.br + +.IP "\fBGSI h g\fP - GPIO claim for input (simple)" +.IP "" 4 + +.br +This command claims GPIO \fBg\fP for input. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 gsi 1 23 # claim GPIO 23 for input. +.br + +.EE + +.br + +.IP "\fBGSIX h lf g\fP - GPIO claim for input" +.IP "" 4 + +.br +This command claims GPIO \fBg\fP for input. + +.br +The line flags \fBlf\fP may be used to set the GPIO +as active low, open drain, or open source. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 gsi 1 0 23 # claim GPIO 23 for input. +.br + +.EE + +.br + +.IP "\fBGSO h g\fP - GPIO claim for output (simple)" +.IP "" 4 + +.br +This command claims GPIO \fBg\fP for output. + +.br + +.br +The GPIO will be initialised low. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 gso 1 25 # claim GPIO 25 for low output. +.br + +.EE + +.br + +.IP "\fBGSOX h lf g v\fP - GPIO claim for output" +.IP "" 4 + +.br +This command claims GPIO \fBg\fP for output. + +.br +The line flags \fBlf\fP may be used to set the GPIO +as active low, open drain, or open source. + +.br +If \fBv\fP is zero the GPIO will be initialised low. If any other +value is used the GPIO will be initialised high. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 gso 1 0 25 # claim GPIO 25 for high output. +.br + +.EE + +.br + +.IP "\fBGSA h g nfyh\fP - GPIO claim for alerts (simple)" +.IP "" 4 + +.br +This command claims GPIO \fBg\fP for alerts. + +.br +Alerts will be generated for both edges. + +.br +The alerts will be sent to a previously opened notification pipe \fBnfyh\fP. + +.br + +.IP "\fBGSAX h lf ef g nfyh\fP - GPIO claim for alerts" +.IP "" 4 + +.br +This command claims GPIO \fBg\fP for alerts. + +.br +The line flags \fBlf\fP may be used to set the GPIO +as active low, open drain, or open source. + +.br +The event flags \fBef\fP specify whether alerts should be +generated on a rising edge, falling edge, or both edges. + +.br +The alerts will be sent to a previously opened notification pipe \fBnfyh\fP. + +.br + +.IP "\fBGSF h g\fP - GPIO free" +.IP "" 4 + +.br +This command releases GPIO \fBg\fP. The GPIO +may now be claimed by another user or for a different purpose. + +.br + +.IP "\fBGSGI h g*\fP - GPIO group claim for inputs (simple)" +.IP "" 4 + +.br +This command claims a group of GPIO for inputs. + +.br +\fBg*\fP 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. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 gsgi 1 16 17 18 19 20 21 +.br + +.EE + +.br + +.IP "\fBGSGIX h lf g*\fP - GPIO group claim for inputs" +.IP "" 4 + +.br +This command claims a group of GPIO for inputs. All the GPIO +share the same line flag setting. + +.br +The line flags \fBlf\fP may be used to set the GPIO +as active low, open drain, or open source. + +.br +\fBg*\fP 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. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 gsgix 1 0 16 17 18 19 20 21 +.br + +.EE + +.br + +.IP "\fBGSGO h g*\fP - GPIO group claim for outputs (simple)" +.IP "" 4 + +.br +This command claims a group of GPIO for outputs. + +.br +\fBg*\fP 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. + +.br +The GPIO will be initialised low. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 gsgo 1 22 23 24 25 +.br + +.EE + +.br + +.IP "\fBGSGOX h lf g* v*\fP - GPIO group claim for outputs" +.IP "" 4 + +.br +This command claims a group of GPIO for outputs. All the GPIO +and share the same line flag setting. + +.br +The line flags \fBlf\fP may be used to set the GPIO +as active low, open drain, or open source. + +.br +\fBg*\fP 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. + +.br +\fBv*\fP is a list of initialisation values for the GPIO. If a value is +zero the corresponding GPIO will be initialised low. If any other +value is used the corresponding GPIO will be initialised high. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 gsgox 1 0 22 23 24 25 1 1 1 1 +.br + +.EE + +.br + +.IP "\fBGSGF h g\fP - GPIO group free" +.IP "" 4 + +.br +This command releases the group of GPIO identified by the group +leader \fBg\fP. The GPIO +may now be claimed by another user or for a different purpose. + +.br + +\fBExample\fP +.br + +.EX +rgs c 1 gsgf 1 22 +.br + +.EE + +.br + +.IP "\fBGR h g\fP - GPIO read" +.IP "" 4 + +.br +This command returns the current value (0 or 1) of GPIO \fBg\fP. + +.br +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. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 gr 1 22 +.br +1 +.br + +.EE + +.br + +.IP "\fBGW h g v\fP - GPIO write" +.IP "" 4 + +.br +This command sets the value (0 or 1) of GPIO \fBg\fP. + +.br +This command will work for any GPIO claimed as an output +(even if a member of a group). + +.br +If \fBv\fP is zero the GPIO will be set low. +If any other value is used the GPIO will be set high. + +.br + +.IP "\fBGGR h g\fP - GPIO group read" +.IP "" 4 + +.br +This command reads a group of GPIO identified by group leader \fBg\fP. + +.br +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. + +.br +Two values are returned. The first is the group size (the number of +GPIO in the group). The second is the group bits as a decimal value. + +.br +Bit 0 is the level of the group leader. +.br +Bit 1 is the level of the second GPIO in the group. +.br +Bit g is the level of GPIO g+1 in the group. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 gsgi 1 0 16 17 18 19 20 21 +.br +$ rgs c 1 ggr 1 16 +.br +6 49 # six GPIO, group leader (16) high, 17-19 low, 20-21 high +.br + +.EE + +.br + +.IP "\fBGGW h g gbits\fP - GPIO group write (simple)" +.IP "" 4 + +.br +This command writes a group of GPIO identified by group leader \fBg\fP. + +.br +The values of each GPIO of the group are set according to the bits +.br +of \fBgbits\fP. + +.br +Bit 0 sets the level of the group leader. +.br +Bit 1 sets the level of the second GPIO in the group. +.br +Bit g sets the level of GPIO g+1 in the group. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 ggr 1 22 +.br +4 15 +.br +$ rgs c 1 ggw 1 22 5 +.br +$ rgs c 1 ggr 1 22 +.br +4 5 +.br +$ rgs c 1 ggw 1 22 10 +.br +$ rgs c 1 ggr 1 22 +.br +4 10 +.br + +.EE + +.br + +.IP "\fBGGWX h g gbits gmask\fP - GPIO group write" +.IP "" 4 + +.br +This command writes a group of GPIO identified by group leader \fBg\fP. + +.br +The values of each GPIO of the group are set according to the bits +.br +of \fBgbits\fP. + +.br +Bit 0 sets the level of the group leader. +.br +Bit 1 sets the level of the second GPIO in the group. +.br +Bit g sets the level of GPIO g+1 in the group. + +.br +However this may be modified by the \fBgmask\fP. A GPIO is only +updated if the corresponding bit in the mask is 1. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 ggr 1 22 +.br +4 15 +.br +$ rgs c 1 ggw 1 22 5 15 +.br +$ rgs c 1 ggr 1 22 +.br +4 5 +.br +$ rgs c 1 ggw 1 22 10 0 +.br +$ rgs c 1 ggr 1 22 +.br +4 5 +.br +$ rgs c 1 ggw 1 22 10 15 +.br +$ rgs c 1 ggr 1 22 +.br +4 10 +.br + +.EE + +.br + +.IP "\fBGP h g mon moff\fP - GPIO tx pulse (simple)" +.IP "" 4 + +.br +This command starts software timed pulses on GPIO \fBg\fP . + +.br +Each cycle consists of \fBmon\fP microseconds of GPIO high +followed by \fBmoff\fP microseconds of GPIO low. + +.br +PWM is characterised by two values, its frequency (number of cycles +per second) and its duty cycle (percentage of high time per cycle). + +.br +The set frequency will be 1000000 / (mon + moff) Hz. + +.br +The set duty cycle will be mon / (mon + moff) * 100 %. + +.br +E.g. if mon is 50 and moff is 100 the frequency will be 6666.67 Hz +and the duty cycle will be 33.33 %. + +.br + +.IP "\fBGPX h g mon moff off cyc\fP - GPIO tx pulse" +.IP "" 4 + +.br +This command starts software timed pulses on GPIO \fBg\fP . + +.br +\fBcyc\fP cycles are transmitted (0 means infinite). Each cycle +consists of \fBmon\fP microseconds of GPIO high followed by \fBmoff\fP +microseconds of GPIO low. + +.br +PWM is characterised by two values, its frequency (number of cycles +per second) and its duty cycle (percentage of high time per cycle). + +.br +The set frequency will be 1000000 / (mon + moff) Hz. + +.br +The set duty cycle will be mon / (mon + moff) * 100 %. + +.br +E.g. if mon is 50 and moff is 100 the frequency will be 6666.67 Hz +and the duty cycle will be 33.33 %. + +.br +\fBoff\fP is a microsecond offset from the natural start of the PWM cycle. + +.br +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. + +.br +Another command may be issued to the GPIO before the last has finished. + +.br +If the last command had infinite cycles (\fBcyc\fP 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 \fBcyc\fP cycles. + +.br +Multiple pulse settings may be queued in this way. + +.br + +.IP "\fBP h g pf pdc\fP - GPIO tx PWM (simple)" +.IP "" 4 + +.br +This command starts software timed PWM on GPIO \fBg\fP . + +.br +PWM is characterised by two values, its frequency (number of cycles +per second) and its duty cycle (percentage of high time per cycle). + +.br + +.IP "\fBPX h g pf pdc off cyc\fP - GPIO tx PWM" +.IP "" 4 + +.br +This command starts software timed PWM on GPIO \fBg\fP . + +.br +PWM is characterised by two values, its frequency (number of cycles +per second) and its duty cycle (percentage of high time per cycle). + +.br +\fBoff\fP is a microsecond offset from the natural start of the PWM cycle. + +.br +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. + +.br +Another PWM command may be issued to the GPIO before the last has finished. + +.br +If the last PWM had infinite cycles (\fBcyc\fP 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 \fBcyc\fP cycles. + +.br +Multiple PWM settings may be queued in this way. + +.br + +.IP "\fBS h g spw\fP - GPIO tx servo pulses (simple)" +.IP "" 4 + +.br +This command starts software timed servo pulses on GPIO \fBg\fP . + +.br +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. + +.br + +.IP "\fBSX h g spw sf off cyc\fP - GPIO tx servo pulses" +.IP "" 4 + +.br +This command starts software timed servo pulses on GPIO \fBg\fP . + +.br +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. + +.br +Another servo command may be issued to the GPIO before the last has finished. + +.br +If the last command had infinite cycles (\fBcyc\fP 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 \fBcyc\fP cycles. + +.br +Multiple servo settings may be queued in this way. + +.br + +.IP "\fBGWAVE h g p*\fP - GPIO group tx wave" +.IP "" 4 + +.br +This command starts a wave on GPIO group \fBg\fP . + +.br +\fBp\fP is a series of pulses to be transmitted on the GPIO group. + +.br +Each pulse is defined by the following triplet: + +.br +\fBgbits\fP the levels to set for the selected GPIO +.br +\fBgmask\fP the GPIO to select +.br +\fBus\fP the delay in microseconds before the next pulse + +.br +Another wave command may be issued to the GPIO group before the +last has finished transmission. + +.br +Multiple waves may be queued in this way. + +.br + +.IP "\fBGBUSY h g k\fP - GPIO or group tx busy +.br" +.IP "" 4 + +.br +This command checks to see if a specified kind \fBk\fP of transmission +is ongoing on a GPIO or GPIO group \fBg\fP . + +.br +The command returns 1 if transmission is ongoing, otherwise 0. + +.br + +.IP "\fBGROOM h g k\fP - GPIO or group tx entries" +.IP "" 4 + +.br +This returns the number of slots there are to queue further +transmissions of a specified kind \fBk\fP in the tx queue for +GPIO or GPIO group \fBg\fP. + +.br +The command returns the number of free slots (0 for no free slots). + +.br + +.IP "\fBGDEB h g us\fP - GPIO debounce time" +.IP "" 4 + +.br +This command sets the debounce time for GPIO \fBg\fP to \fBus\fP microseconds. + +.br +This command is only effective when the GPIO is being used as +a source of alerts. + +.br +Any level changes shorter than the debounce setting will be +discarded, i.e. they will not generate an alert. + +.br +Reported level changes will be timestamped \fBus\fP microseconds +after the level change. + +.br + +.IP "\fBGWDOG h g us\fP - GPIO watchdog time" +.IP "" 4 + +.br +This command sets the watchdog time for GPIO \fBg\fP + to \fBus\fP microseconds. + +.br +This only affects alerts. + +.br +A watchdog alert will be sent if no edge alert has been issued +for that GPIO in the previous watchdog microseconds. + +.br +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. + +.br +The level is set to 2 for a watchdog alert. + +.br +.SS I2C +.br + +.IP "\fBI2CO ib id if\fP - I2C open device" +.IP "" 4 + +.br +This is a privileged command. See \fBpermits\fP. + +.br +This command returns a handle to access device \fBid\fP on I2C bus \fBib\fP. +The device is opened with flags \fBif\fP. + +.br +No flags are currently defined. The parameter \fBif\fP should be 0. + +.br +Upon success the next free handle (>=0) is returned. On error a +negative status code will be returned. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 i2co 1 0x70 0 # Bus 1, device 0x70, flags 0. +.br +0 +.br + +.br +$ rgs c 1 i2co 1 0x53 0 # Bus 1, device 0x53, flags 0. +.br +1 +.br + +.EE + +.br + +.IP "\fBI2CC h\fP - I2C close device" +.IP "" 4 +This command closes an I2C device previously opened by \fBI2CO\fP. + +.br +Upon success nothing is returned. On error a negative status code +will be returned. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 i2cc 0 # First close okay. +.br + +.br +$ rgs c 1 i2cc 0 # Second fails. +.br +-25 +.br +ERROR: unknown handle +.br + +.EE + +.br + +.IP "\fBI2CWQ h bit\fP - SMB Write Quick: write bit" +.IP "" 4 + +.br +This command writes a single \fBbit\fP to the I2C device. + +.br +Upon success nothing is returned. On error a negative status code +will be returned. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 i2cwq 0 1 +.br + +.EE + +.br + +.IP "\fBI2CRS h\fP - SMB Read Byte: read byte" +.IP "" 4 + +.br +This command returns a single byte read from the I2C device. + +.br +Upon success a value between 0 and 255 will be returned. On error +a negative status code will be returned. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 i2crs 0 +.br +0 +.br + +.EE + +.br + +.IP "\fBI2CWS h bv\fP - SMB Write Byte: write byte" +.IP "" 4 + +.br +This command writes a single byte \fBbv\fP to the I2C device. + +.br +Upon success nothing is returned. On error a negative status code +will be returned. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 i2cws 0 0x12 +.br + +.br +$ rgs c 1 i2cws 0 0xff +.br +-82 +.br +ERROR: I2C write failed +.br + +.EE + +.br + +.IP "\fBI2CRB h r\fP - SMB Read Byte Data: read byte from register" +.IP "" 4 + +.br +This command returns a single byte read from register \fBr\fP of +the I2C device. + +.br +Upon success a value between 0 and 255 will be returned. On error +a negative status code will be returned. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 i2crb 0 0 +.br +6 +.br + +.EE + +.br + +.IP "\fBI2CWB h r bv\fP - SMB Write Byte Data: write byte to register" +.IP "" 4 + +.br +This command writes a single byte \fBbv\fP to register \fBr\fP of the +I2C device. + +.br +Upon success nothing is returned. On error a negative status code +will be returned. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 i2cwb 0 10 0x54 +.br + +.EE + +.br + +.IP "\fBI2CRW h r\fP - SMB Read Word Data: read word from register" +.IP "" 4 + +.br +This command returns a single 16 bit word read from register \fBr\fP of +the I2C device. + +.br +Upon success a value between 0 and 65535 will be returned. On error +a negative status code will be returned. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 i2crw 0 0 +.br +6150 +.br + +.EE + +.br + +.IP "\fBI2CWW h r wv\fP - SMB Write Word Data: write word to register" +.IP "" 4 + +.br +This command writes a single 16 bit word \fBwv\fP to register \fBr\fP of +the I2C device. + +.br +Upon success nothing is returned. On error a negative status code +will be returned. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 i2cww 0 0 0xffff +.br + +.EE + +.br + +.IP "\fBI2CRK h r\fP - SMB Read Block Data: read data from register" +.IP "" 4 + +.br +This command returns between 1 and 32 bytes read from register \fBr\fP of +the I2C device. + +.br +Upon success the count of returned bytes followed by the bytes themselves +is returned. On error a negative status code will be returned. + +.br +The number of bytes of returned data is specific to the device and +register. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 i2crk 0 0 +.br +6 0 0 0 0 0 0 +.br + +.br +$ rgs c 1 i2crk 0 1 +.br +24 0 0 0 0 0 0 0 0 0 0 0 0 120 222 105 215 128 87 195 217 0 0 0 0 +.br + +.EE + +.br + +.IP "\fBI2CWK h r bvs\fP - SMB Write Block Data: write data to register" +.IP "" 4 + +.br +This command writes between 1 and 32 bytes \fBbvs\fP to register \fBr\fP of +the I2C device. + +.br +Upon success nothing is returned. On error a negative status code +will be returned. + +.br + +\fBExample\fP +.br + +.EX +rgs c 1 i2cwk 0 4 0x01 0x04 0xc0 +.br + +.EE + +.br + +.IP "\fBI2CRI h r num\fP - SMB Read I2C Block Data: read bytes from register" +.IP "" 4 + +.br +This command returns \fBnum\fP bytes from register \fBr\fP of +the I2C device. + +.br +Upon success the count of returned bytes followed by the bytes themselves +is returned. On error a negative status code will be returned. + +.br +The parameter \fBnum\fP may be 1-32. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 i2cri 0 0 16 +.br +16 237 155 155 155 155 155 155 155 155 155 155 155 155 155 155 155 +.br + +.EE + +.br + +.IP "\fBI2CWI h r bvs\fP - SMB Write I2C Block Data" +.IP "" 4 + +.br +This command writes between 1 and 32 bytes \fBbvs\fP to register \fBr\fP of +the I2C device. + +.br +Upon success nothing is returned. On error a negative status code +will be returned. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 i2cwi 0 4 0x01 0x04 0xc0 +.br + +.EE + +.br + +.IP "\fBI2CRD h num\fP - I2C read device" +.IP "" 4 + +.br +This command returns \fBnum\fP bytes read from the I2C device. + +.br +Upon success the count of returned bytes followed by the bytes themselves +is returned. On error a negative status code will be returned. + +.br +This command operates on the raw I2C device. The maximum value of the +parameter \fBnum\fP is dependent on the I2C drivers and the device +itself. rgs imposes a limit of about 8000 bytes. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 i2crd 0 16 +.br +16 6 24 0 0 0 0 0 0 0 0 0 0 0 0 32 78 +.br + +.EE + +.br + +.IP "\fBI2CWD h bvs\fP - I2C write device" +.IP "" 4 + +.br +This command writes a block of bytes \fBbvs\fP to the I2C device. + +.br +Upon success nothing is returned. On error a negative status code +will be returned. + +.br +The number of bytes which may be written in one transaction is +dependent on the I2C drivers and the device itself. rgs imposes +a limit of about 500 bytes. + +.br +This command operates on the raw I2C device. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 i2cwd 0 0x01 0x02 0x03 0x04 +.br + +.EE + +.br + +.IP "\fBI2CPC h r wv\fP - SMB Process Call: exchange register with word" +.IP "" 4 +This command writes \fBwv\fP to register \fBr\fP of the I2C device +and returns a 16-bit word read from the device. + +.br +Upon success a value between 0 and 65535 will be returned. On error +a negative status code will be returned. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 i2cpc 0 37 43210 +.br +39933 +.br + +.br +$ rgs c 1 i2cpc 0 256 43210 +.br +ERROR: bad i2c/spi/ser parameter +.br +-81 +.br + +.EE + +.br + +.IP "\fBI2CPK h r bvs\fP - SMB Block Process Call: exchange data bytes with register" +.IP "" 4 + +.br +This command writes the data bytes \fBbvs\fP to register \fBr\fP of +the I2C device and returns a device specific number of bytes. + +.br +Upon success the count of returned bytes followed by the bytes themselves +is returned. On error a negative status code will be returned. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 i2cpk 0 0 0x11 0x12 +.br +6 0 0 0 0 0 0 +.br + +.EE + +.br + +.IP "\fBI2CZ h bvs\fP - I2C zip" +.IP "" 4 +This command executes a sequence of I2C operations. The +operations to be performed are specified by the contents of \fBbvs\fP +which contains the concatenated command codes and associated data. + +.br +The following command codes are supported: + +.br + +.EX +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 + +.EE + +.br +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). + +.br +The address defaults to that associated with the handle \fBh\fP. +The flags default to 0. The address and flags maintain their +previous value until updated. + +.br + +\fBExample\fP +.br + +.EX +Set address 0x53, write 0x32, read 6 bytes +.br +Set address 0x1E, write 0x03, read 6 bytes +.br +Set address 0x68, write 0x1B, read 8 bytes +.br +End +.br + +.br +2 0x53 5 1 0x32 4 6 +.br +2 0x1E 5 1 0x03 4 6 +.br +2 0x68 5 1 0x1B 4 8 +.br +0 +.br + +.EE + +.br +.SS NOTIFICATIONS +.br + +.IP "\fBNO \fP - Notification open" +.IP "" 4 + +.br +This is a privileged command. See \fBpermits\fP. + +.br +This command requests a free notification handle. + +.br +A notification is a method for being notified of GPIO state changes via a pipe. + +.br +Upon success the command returns a handle greater than or equal to zero. +On error a negative status code will be returned. + +.br +The pipes are created in the daemon's working directory (the command +\fBpwd\fP will show the working directory). + +.br +Notifications for handle g will be available at the pipe named lgd-nfyx +(where g is the handle number). + +.br +E.g. if the command returns 15 then the notifications must be read +from lgd-nfy15. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 no +.br +0 +.br + +.EE + +.br + +.IP "\fBNC h\fP - Notification close" +.IP "" 4 + +.br +This command closes a notification previously opened by \fBNO\fP. + +.br +Upon success nothing is returned. On error a negative status code +will be returned. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 nc 0 # First call succeeds. +.br + +.br +$ rgs c 1 nc 1 # Second call fails. +.br +-5 +.br +ERROR: unknown handle +.br + +.EE + +.br + +.IP "\fBNP h\fP - Notification pause" +.IP "" 4 + +.br +This command pauses notifications. + +.br +Upon success nothing is returned. On error a negative status code +will be returned. + +.br +Notifications for the handle are paused until a \fBNR\fP command. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 np 0 +.br + +.EE + +.br + +.IP "\fBNR h\fP - Notification resume" +.IP "" 4 + +.br +This command resumes notifications. + +.br +Upon success nothing is returned. On error a negative status code +will be returned. + +.br + +\fBExample\fP +.br + +.EX +$ rgs c 1 nr 0 +.br + +.br +$ rgs c 1 nr 1 +.br +-5 +.br +ERROR: unknown handle +.br + +.EE + +.br +.SS SCRIPTS +.br + +.IP "\fBPROC t\fP - Script store" +.IP "" 4 + +.br +This is a privileged command. See \fBpermits\fP. + +.br +This command stores a script \fBt\fP for later execution. + +.br +If the script is valid a handle (>=0) is returned which is passed +to the other script commands. On error a negative status code +will be returned. + +.br + +\fBExample\fP +.br + +.EX +$ rgs proc tag 123 w 4 0 mils 200 w 4 1 mils 300 dcr p0 jp 123 +.br +0 +.br + +.br +$ rgs proc tag 123 w 4 0 mils 5 w 4 1 mils 5 jmp 12 +.br +ERROR: script has unresolved tag +.br +-63 +.br + +.EE + +.br + +.IP "\fBPROCR h pars\fP - Script run" +.IP "" 4 + +.br +This command runs stored script \fBh\fP passing it up to 10 optional +parameters. + +.br +Upon success nothing is returned. On error a negative status code +will be returned. + +.br + +\fBExample\fP +.br + +.EX +$ rgs proc tag 123 w 4 0 mils 200 w 4 1 mils 300 dcr p0 jp 123 +.br +0 +.br + +.br +$ rgs procr 0 50 # Run script 0 with parameter 0 of 50. +.br + +.br +$ rgs procp 0 +.br +2 44 0 0 0 0 0 0 0 0 0 +.br +$ rgs procp 0 +.br +2 37 0 0 0 0 0 0 0 0 0 +.br +$ rgs procp 0 +.br +2 10 0 0 0 0 0 0 0 0 0 +.br +$ rgs procp 0 +.br +2 5 0 0 0 0 0 0 0 0 0 +.br +$ rgs procp 0 +.br +2 2 0 0 0 0 0 0 0 0 0 +.br +$ rgs procp 0 +.br +1 -1 0 0 0 0 0 0 0 0 0 +.br + +.EE + +.br + +.IP "\fBPROCU h pars\fP - Script update parameters" +.IP "" 4 + +.br +This command sets the parameters of a stored script \fBh\fP passing +it up to 10 parameters. + +.br +Upon success nothing is returned. On error a negative status code +will be returned. + +.br + +\fBExample\fP +.br + +.EX +$ rgs proc tag 0 hp 18 p0 p1 mils 1000 jmp 0 +.br +0 +.br +$ rgs procu 0 50 500000 +.br +$ rgs procr 0 +.br +$ rgs procu 0 100 +.br +$ rgs procu 0 200 +.br +$ rgs procu 0 200 100000 +.br + +.EE + +.br + +.IP "\fBPROCP h\fP - Script get status and parameters" +.IP "" 4 + +.br +This command returns the status of script \fBh\fP as well as the +current value of its 10 parameters. + +.br +Upon success the script status and parameters are returned. +On error a negative status code will be returned. + +.br +The script status may be one of + +.br + +.EX +0 being initialised +1 ready +2 running +3 waiting +4 ended +5 halted +6 failed + +.EE + +.br + +\fBExample\fP +.br + +.EX +$ rgs procp 0 +.br +1 0 0 0 0 0 0 0 0 0 0 +.br + +.EE + +.br + +.IP "\fBPROCS h\fP - Script stop" +.IP "" 4 + +.br +This command stops a running script \fBh\fP. + +.br +Upon success nothing is returned. On error a negative status code +will be returned. + +.br + +\fBExample\fP +.br + +.EX +$ rgs procs 0 +.br + +.br +$ rgs procs 1 +.br +-5 +.br +ERROR: unknown handle +.br + +.EE + +.br + +.IP "\fBPROCD h\fP - Script delete" +.IP "" 4 + +.br +This command deletes script \fBh\fP. + +.br +Upon success nothing is returned. On error a negative status code +will be returned. + +.br + +\fBExample\fP +.br + +.EX +$ rgs procd 1 +.br + +.br +$ rgs procd 1 +.br +ERROR: unknown handle +.br +-5 +.br + +.EE + +.br + +.IP "\fBPARSE t\fP - Script validate" +.IP "" 4 + +.br +Validates the text \fBt\fP of a script without storing the script. + +.br +Upon success nothing is returned. On error a list of detected +script errors will be given. + +.br +This command may be used to find script syntax faults. + +.br + +\fBExample\fP +.br + +.EX +$ rgs parse tag 100 w 22 1 mils 200 w 22 0 mils 800 jmp 100 +.br + +.br +$ rgs parse tag 0 w 22 1 mills 50 w 22 0 dcr p10 jp 99 +.br +Unknown command: mills +.br +Unknown command: 50 +.br +Bad parameter to dcr +.br +Can't resolve tag 99 +.br + +.EE + +.br +.SS SERIAL +.br + +.IP "\fBSERO dev b sef\fP - Serial open device" +.IP "" 4 + +.br +This is a privileged command. See \fBpermits\fP. + +.br +This command opens the serial \fBdev\fP at \fBb\fP bits per second. + +.br +No flags are currently defined. \fBsef\fP should be set to zero. + +.br +Upon success a handle (>=0) is returned. On error a negative status code +will be returned. + +.br +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. + +.br + +\fBExample\fP +.br + +.EX +$ rgs sero ttyAMA0 9600 0 +.br +0 +.br + +.br +$ rgs sero tty1 38400 0 +.br +1 +.br + +.EE + +.br + +.IP "\fBSERC h\fP - Serial close device" +.IP "" 4 + +.br +This command closes a serial device previously opened by \fBSERO\fP. + +.br +Upon success nothing is returned. On error a negative status code +will be returned. + +.br + +\fBExample\fP +.br + +.EX +$ rgs serc 0 # First close okay. +.br + +.br +$ rgs serc 0 # Second close gives error. +.br +-25 +.br +ERROR: unknown handle +.br + +.EE + +.br + +.IP "\fBSERRB \fP - Serial read byte" +.IP "" 4 + +.br +This command returns a byte of data read from the serial +device. + +.br +Upon success a number between 0 and 255 is returned. +On error a negative status code will be returned. + +.br + +\fBExample\fP +.br + +.EX +$ rgs serrb 0 +.br +23 +.br +$ rgs serrb 0 +.br +45 +.br + +.EE + +.br + +.IP "\fBSERWB h bv\fP - Serial write byte" +.IP "" 4 + +.br +This command writes a single byte \fBbv\fP to the serial device. + +.br +Upon success nothing is returned. On error a negative status code +will be returned. + +.br + +\fBExample\fP +.br + +.EX +$ rgs serwb 0 23 +.br +$ rgs serwb 0 0xf0 +.br + +.EE + +.br + +.IP "\fBSERR h num\fP - Serial read bytes" +.IP "" 4 + +.br +This command returns up to \fBnum\fP bytes of data read from the +serial device. + +.br +Upon success the count of returned bytes followed by the bytes themselves +is returned. On error a negative status code will be returned. + +.br + +\fBExample\fP +.br + +.EX +$ rgs serr 0 10 +.br +5 48 49 128 144 255 +.br + +.br +$ rgs serr 0 10 +.br +0 +.br + +.EE + +.br + +.IP "\fBSERW h bvs\fP - Serial write bytes" +.IP "" 4 + +.br +This command writes bytes \fBbvs\fP to the serial device. + +.br +Upon success nothing is returned. On error a negative status code +will be returned. + +.br + +\fBExample\fP +.br + +.EX +$ rgs serw 0 23 45 67 89 +.br + +.EE + +.br + +.IP "\fBSERDA h\fP - Serial data available" +.IP "" 4 + +.br +This command returns the number of bytes of data available +to be read from the serial device. + +.br +Upon success the count of bytes available to be read is +returned (which may be 0). On error a negative status code +will be returned. + +.br + +\fBExample\fP +.br + +.EX +$ rgs serda 0 +.br +0 +.br + +.EE + +.br +.SS SHELL +.br + +.IP "\fBSHELL name str\fP - Execute a shell command" +.IP "" 4 + +.br +This is a privileged command. See \fBpermits\fP. + +.br +This command uses the system call to execute a shell script \fBname\fP +with the given string \fBstr\fP as its parameter. + +.br +Upon success the exit status of the system call is returned. On error a negative status code will be returned. + +.br +\fBname\fP must exist in a directory named cgi in the daemon's +configuration directory and must be executable. + +.br +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. + +.br +The following table gives some example returned statuses. + +.br + +.EX +Script exit status Returned system call status +1 256 +5 1280 +10 2560 +200 51200 +script not found 32512 + +.EE + +.br + +\fBExample\fP +.br + +.EX +# pass two parameters, hello and world +.br +$ rgs shell scr1 hello world +.br +256 +.br + +.br +# pass three parameters, hello, string with spaces, and world +.br +$ rgs shell scr1 "hello 'string with spaces' world" +.br +256 +.br + +.br +# pass one parameter, hello string with spaces world +.br +$ rgs shell scr1 "\"hello string with spaces world\"" +.br +256 +.br + +.br +# non-existent script +.br +$ rgs shell scr78 par1 +.br +32512 +.br + +.EE + +.br +.SS SPI +.br + +.IP "\fBSPIO spd spc b spf\fP - SPI open device" +.IP "" 4 + +.br +This is a privileged command. See \fBpermits\fP. + +.br +Upon success a handle is returned. On error a negative status code +will be returned. + +.br +Data will be transferred at \fBb\fP bits per second. The flags \fBspf\fP +may be used to modify the default behaviour. + +.br +The flags consists of the least significant 2 bits. + +.br + +.EX +1 0 +.br +m m +.br + +.EE + +.br +mm defines the SPI mode. + +.br + +.EX +Mode POL PHA +.br + 0 0 0 +.br + 1 0 1 +.br + 2 1 0 +.br + 3 1 1 +.br + +.EE + +.br + +.br + +.IP "\fBSPIC h\fP - SPI close device" +.IP "" 4 + +.br +This command closes a SPI device previously opened by \fBSPIO\fP. + +.br +Upon success nothing is returned. On error a negative status code +will be returned. + +.br + +\fBExample\fP +.br + +.EX +$ rgs spic 1 +.br + +.br +$ rgs spic 1 +.br +-25 +.br +ERROR: unknown handle +.br + +.EE + +.br + +.IP "\fBSPIR h num\fP - SPI read bytes" +.IP "" 4 + +.br +This command returns \fBnum\fP bytes read from the SPI device. + +.br +Upon success the count of returned bytes followed by the bytes themselves +is returned. On error a negative status code will be returned. + +.br + +\fBExample\fP +.br + +.EX +$ rgs spir 0 10 # Read 10 bytes from the SPI device. +.br +10 0 0 0 0 0 0 0 0 0 0 +.br + +.EE + +.br + +.IP "\fBSPIW h bvs\fP - SPI write bytes" +.IP "" 4 + +.br +This command writes bytes \fBbvs\fP to the SPI device. + +.br +Upon success nothing is returned. On error a negative status code +will be returned. + +.br + +\fBExample\fP +.br + +.EX +$ rgs spiw 0 0x22 0x33 0xcc 0xff +.br + +.EE + +.br + +.IP "\fBSPIX h bvs\fP - SPI transfer bytes" +.IP "" 4 + +.br +This command writes bytes \fBbvs\fP to the SPI device. + +.br +It returns the same number of bytes read from the device. + +.br +Upon success the count of returned bytes followed by the bytes themselves +is returned. On error a negative status code will be returned. + +.br + +\fBExample\fP +.br + +.EX +$ rgs spix 0 0x22 0x33 0xcc 0xff +.br +4 0 0 0 0 +.br + +.EE + +.br +.SS UTILITIES +.br + +.IP "\fBLGV \fP - Get lg library version" +.IP "" 4 + +.br +This command returns the lg library version. + +.br + +\fBExample\fP +.br + +.EX +$ rgs lgv +.br +lg_0.1.0.0 +.br + +.EE + +.br + +.IP "\fBSBC \fP - Get SBC's host name" +.IP "" 4 + +.br +This command returns the rgpiod daemon server name. + +.br + +\fBExample\fP +.br + +.EX +$ rgs sbc +.br +venus +.br + +.EE + +.br + +.IP "\fBCGI cid\fP - Get internal configuration setting" +.IP "" 4 + +.br +This is a privileged command. See \fBpermits\fP. + +.br +This command returns the value of an internal library +configuration setting \fBcid\fP. + +.br + +\fBExample\fP +.br + +.EX +$ rgs cgi 0 +.br +1 +.br + +.EE + +.br + +.IP "\fBCSI cid v\fP - Set internal configuration setting" +.IP "" 4 + +.br +This is a privileged command. See \fBpermits\fP. + +.br +This command sets the value of the internal library +configuration setting \fBcid\fP to \fBv\fP. + +.br + +\fBExample\fP +.br + +.EX +$ rgs csi 0 3 +.br +$ rgs cgi 0 +.br +3 +.br + +.EE + +.br + +.IP "\fBT/TICK \fP - Get nanoseconds since the epoch" +.IP "" 4 + +.br +T and TICK are synonyms. + +.br +This command returns the number of nanoseconds since the epoch +(start of 1970). + +.br + +\fBExample\fP +.br + +.EX +$ rgs t +.br +1601838936723095901 +.br +$ rgs tick +.br +1601838940792322758 +.br + +.EE + +.br + +.IP "\fBMICS v\fP - Microseconds delay" +.IP "" 4 +This command delays execution for \fBv\fP microseconds. + +.br +Upon success nothing is returned. On error a negative status code +will be returned. + +.br +The main use of this command is expected to be within scripts. + +.br + +\fBExample\fP +.br + +.EX +$ rgs mics 20 # Delay 20 microseconds. +.br +$ rgs mics 1000000 # Delay 1 second. +.br +$ rgs mics 5100000 # Delay 5.1 seconds. +.br +-24 +.br +ERROR: bad MICS delay (too large) +.br + +.EE + +.br + +.IP "\fBMILS v\fP - Milliseconds delay" +.IP "" 4 + +.br +This command delays execution for \fBv\fP milliseconds. + +.br +Upon success nothing is returned. On error a negative status code +will be returned. + +.br + +\fBExample\fP +.br + +.EX +$ rgs mils 2000 # Delay 2 seconds. +.br +$ rgs mils 301000 # Delay 301 seconds. +.br +-25 +.br +ERROR: bad MILS delay (too large) +.br + +.EE + +.br + +.IP "\fBU/USER \fP - Set user" +.IP "" 4 + +.br +U and USER are synonyms. + +.br +This command sets the current user and associated permissions. + +.br + +\fBExample\fP +.br + +.EX +$ rgs u test1 # set user test1 +.br +$ rgs user test1 # set user test1 +.br +$ rgs u testx # unknown user +.br +-95 +.br +ERROR: bad secret for user +.br + +.EE + +.br + +.IP "\fBC/SHARE \fP - Set share" +.IP "" 4 + +.br +C and SHARE are synonyms. + +.br +This command sets the share for handles. + +.br +The command has two uses. Firstly it sets the share id for any +subsequently created handles on the current command line. Secondly +it sets the share id to use to access any previously created handles +on this or earlier command lines. + +.br + +\fBExample\fP +.br + +.EX +rgs c 1 # use share id 1 +.br +rgs share 1 # use share id 1 +.br +rgs c 0 # switch off sharing +.br +rgs share 867 # use share id 867 +.br + +.EE + +.br + +.IP "\fBLCFG \fP - Reload permits configuration file" +.IP "" 4 + +.br +This is a privileged command. See \fBpermits\fP. + +.br +This command reloads the permits configuration file + +.br + +\fBExample\fP +.br + +.EX +$ rgs lcfg +.br +$ rgs lcfg +.br +-93 +.br +ERROR: no permission to perform action +.br +$ rgs lcfg +.br +-93 +.br +ERROR: no permission to perform action +.br + +.EE + +.br + +.IP "\fBPCD \fP - Print daemon configuration directory" +.IP "" 4 + +.br +This command prints the daemon configuration directory + +.br + +\fBExample\fP +.br + +.EX +rgs pcd +.br +/home/joan/LG/TEST +.br + +.EE + +.br + +.IP "\fBPWD \fP - Print daemon working directory" +.IP "" 4 + +.br +This command prints the daemon working directory + +.br + +\fBExample\fP +.br + +.EX +rgs pwd +.br +/home/joan/LG +.br + +.EE + +.br + +.SH PARAMETERS + +.br + +.IP "\fBb\fP: baud" 0 +The command expects the baud rate in bits per second for +the transmission of serial data (I2C/SPI/serial link, waves). + +.br + +.IP "\fBbit\fP: bit value (0-1)" 0 +The command expects 0 or 1. + +.br + +.IP "\fBbv\fP: a byte value (0-255)" 0 +The command expects a byte value. + +.br + +.IP "\fBbvs\fP: byte values (0-255)" 0 +The command expects one or more byte values. + +.br + +.IP "\fBcid\fP: " 0 +A number identifying an internal configuration item. + +.br + +.EX +cid meaning +0 debug level +1 minimum transmission period for PWM and waves + +.EE + +.br + +.IP "\fBcyc\fP: >= 0" 0 +The number of PWM pulses to generate. A value of 0 means infinite. + +.br + +.IP "\fBdev\fP: a tty serial device" 0 +The command expects the name of a serial device without the +leading /dev, e.g. + +.br + +.EX +ttyAMA0 +.br +ttyUSB0 +.br +tty0 +.br +serial0 +.br + +.EE + +.br + +.IP "\fBef\fP: GPIO event flags" 0 + +.br +The following values may be or'd to form the event flags. + +.br + +.EX +Value Meaning +1 Rising edge +2 Falling edge +3 Both edges + +.EE + +.br + +.IP "\fBfile\fP: a file name" 0 +The file name must match an entry in the [files] section of the +permits file. + +.br + +.IP "\fBfrom\fP: 0-2" 0 +Position to seek from \fBFS\fP. + +.br + +.EX + From +0 start +1 current position +2 end + +.EE + +.br + +.IP "\fBg\fP: GPIO" 0 +The command expects a GPIO. + +.br + +.IP "\fBg*\fP: " 0 +A list of one or more GPIO + +.br + +.IP "\fBgbits\fP: " 0 +This value is used to set the levels of a GPIO group. + +.br +Bit 0 represents the level of the group leader. +.br +Bit 1 represents the level of the second GPIO in the group. +.br +Bit g represents the level of GPIO g+1 in the group. + +.br + +.IP "\fBgc\fP: gpiochip (>=0)" 0 +The command expects a gpiochip number. + +.br + +.IP "\fBgmask\fP: " 0 +This value is used to select GPIO from a GPIO group. + +.br +Bit 0 of the mask indicates item 1 +.br +Bit 1 of the mask indicates item 2 +.br +Bit g of the mask indicates item g+1 + +.br +For example suppose the items are GPIO 5, 10, 23, 25, 11. + +.br +Bit 0 of the mask indicates GPIO 5 +.br +Bit 1 of the mask indicates GPIO 10 +.br +Bit 2 of the mask indicates GPIO 23 +.br +Bit 3 of the mask indicates GPIO 25 +.br +Bit 4 of the mask indicates GPIO 11 + +.br +If a bit of the mask is high the corresponding GPIO will be selected. + +.br +E.g. in the above example if the mask has the value 17 GPIO 5 and GPIO +11 will be selected. + +.br + +.IP "\fBh\fP: handle (>=0)" 0 +The command expects a handle. + +.br +A handle is a number referencing an object opened by one of \fBFO\fP, +\fBI2CO\fP, \fBNO\fP, \fBPROC\fP, \fBSERO\fP, \fBSPIO\fP, \fBGO\fP. + +.br + +.IP "\fBib\fP: I2C bus (>=0)" 0 +The command expects an I2C bus number. + +.br + +.IP "\fBid\fP: I2C device (0-0x7F)" 0 +The command expects the address of an I2C device. + +.br + +.IP "\fBif\fP: I2C flags (0)" 0 +The command expects an I2C flags value. No flags are currently defined. + +.br + +.IP "\fBk\fP: " 0 +A kind of transmission. + +.br +0 = PWM +.br +1 = WAVE + +.br + +.IP "\fBlf\fP: GPIO line flags" 0 + +.br +The following values may be or'd to form the line flags. + +.br + +.EX +Value Meaning +4 Active low +8 Open drain +16 Open source + +.EE + +.br + +.IP "\fBmode\fP: lgFile open mode" 0 +One of the following values. + +.br + +.EX + Value Meaning +READ 1 open file for reading +WRITE 2 open file for writing +RW 3 open file for reading and writing + +.EE + +.br +The following values can be or'd into the mode. + +.br + +.EX + Value Meaning +APPEND 4 All writes append data to the end of the file +CREATE 8 The file is created if it doesn't exist +TRUNC 16 The file is truncated + +.EE + +.br + +.IP "\fBmoff\fP: >= 0" 0 +The off period for a PWM pulse in microseconds. + +.br + +.IP "\fBmon\fP: >= 0" 0 +The on period for a PWM pulse in microseconds. + +.br + +.IP "\fBname\fP: the name of a script" 0 + +.br +Only alphanumeric characters, '-' and '_' are allowed in the name. + +.br + +.IP "\fBnfyh\fP: >= 0" 0 + +.br +This associates a notification with a GPIO event. + +.br + +.IP "\fBnum\fP: maximum number of bytes to return (1-)" 0 +The command expects the maximum number of bytes to return. + +.br +For the I2C and SPI commands the requested number of bytes will always +be returned. + +.br +For the serial and file commands the smaller of the number of +bytes available to be read (which may be zero) and \fBnum\fP bytes +will be returned. + +.br + +.IP "\fBoff\fP: >= 0" 0 + +.br +The offset in microseconds from the nominal PWM pulse start. + +.br + +.IP "\fBp*\fP: " 0 +One or more triplets of \fBgbits\fP, \fBgmask\fP, and \fBus\fP +microsecond delay. + +.br + +.IP "\fBpars\fP: script parameters" 0 +The command expects 0 to 10 numbers as parameters to be passed to the script. + +.br + +.IP "\fBpat\fP: a file name pattern" 0 +A file path which may contain wildcards. To be accessible the path +must match an entry in the [files] section of the permits file. + +.br + +.IP "\fBpdc\fP: thousandths of %" 0 +PWM duty cycle between 0 % (0) and 100 % (100000). + +.br + +.IP "\fBpf\fP: thousandths of Hz" 0 +PWM frequency between 0.1 Hz (100) and 10000 Hz (10000000). +Use 0 for off. + +.br + +.IP "\fBr\fP: register (0-255)" 0 +The command expects an I2C register number. + +.br + +.IP "\fBsef\fP: serial flags (32 bits)" 0 +The command expects a flag value. No serial flags are currently defined. + +.br + +.IP "\fBsf\fP: Hz (40-500)" 0 +Servo frequency + +.br + +.IP "\fBspc\fP: SPI channel (>= 0)" 0 +The command expects a SPI channel. + +.br + +.IP "\fBspd\fP: SPI device (>= 0)" 0 +The command expects a SPO device. + +.br + +.IP "\fBspf\fP: SPI flags" 0 +See \fBSPIO\fP. + +.br + +.IP "\fBspw\fP: 0=off, 500-2500 microseconds" 0 +Servo pulse width + +.br + +.IP "\fBstr\fP: a string" 0 +The command expects a string. + +.br + +.IP "\fBt\fP: a string" 0 +The command expects a string. + +.br + +.IP "\fBus\fP: " 0 +The command expects a time interval measured in microseconds. + +.br + +.IP "\fBv\fP: value" 0 +The command expects a number. + +.br + +.IP "\fBv*\fP: " 0 +A list of one or more values. + +.br + +.IP "\fBwv\fP: word value (0-65535)" 0 +The command expects a word value. + +.br + +.SH SEE ALSO + +rgpiod(1), lgpio(3), rgpio(3) diff --git a/rgs.c b/rgs.c new file mode 100644 index 0000000..1372a46 --- /dev/null +++ b/rgs.c @@ -0,0 +1,568 @@ +/* +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 +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lgpio.h" +#include "rgpiod.h" + +#include "lgCmd.h" +#include "lgMD5.h" + +/* +This program provides a socket interface to some of +the commands available from lg. +*/ + +#define RGS_VERSION 0x00000000 + +#define RGS_CONNECT_ERR 255 +#define RGS_OPTION_ERR 254 +#define RGS_SCRIPT_ERR 253 + +char text[CMD_MAX_EXTENSION]; + +int printFlags = 0; + +int status = LG_OKAY; + +#define SOCKET_OPEN_FAILED -1 + +#define PRINT_HEX 1 +#define PRINT_ASCII 2 + +static char *xCmdUsage = "\n\ +C Set share\n\ +CGI cid Get internal configuration setting\n\ +CSI cid v Set internal configuration setting\n\ +\n\ +FC h File close\n\ +FL pat num File list\n\ +FO file mode File open\n\ +FR h num File read\n\ +FS h num from File seek\n\ +FW h bvs File write\n\ +\n\ +GBUSY h g k GPIO or group tx busy\n\ +GC h gpiochip close device\n\ +GDEB h g us GPIO debounce time\n\ +GGR h g GPIO group read\n\ +GGW h g gbits GPIO group write (simple)\n\ +GGWX h g gbits gmask | GPIO group write\n\ +GIC h gpiochip information\n\ +GIL h g gpiochip line information\n\ +GO gc gpiochip open device\n\ +GP h g mon moff GPIO tx pulses (simple)\n\ +GPX h g mon moff off cyc | GPIO tx pulses\n\ +GR h g GPIO read\n\ +GROOM h g k GPIO or group tx entries\n\ +GSA h g GPIO claim for alerts (simple)\n\ +GSAX h lf ef g nfyh | GPIO claim for alerts\n\ +GSF h g GPIO free\n\ +GSGF h g GPIO group free\n\ +GSGI h g* GPIO group claim for inputs (simple)\n\ +GSGIX h lf g* GPIO group claim for inputs\n\ +GSGO h g* GPIO group claim for outputs (simple)\n\ +GSGOX h lf g* v* GPIO group claim for outputs\n\ +GSI h g GPIO claim for input (simple)\n\ +GSIX h lf g GPIO claim for input\n\ +GSO h g GPIO claim for output\n\ +GSOX h lf g v GPIO claim for output\n\ +GW h g v GPIO write\n\ +GWAVE h g p* GPIO group tx wave\n\ +GWDOG h g us GPIO watchdog time\n\ +\n\ +I2CC h I2C close device\n\ +I2CO ib id if I2C open device\n\ +I2CPC h r wv SMB Process Call: exchange register with word\n\ +I2CPK h r bvs SMB Block Process Call: exchange data bytes with register\n\ +I2CRB h r SMB Read Byte Data: read byte from register\n\ +I2CRD h num I2C read device\n\ +I2CRI h r num SMB Read I2C Block Data: read bytes from register\n\ +I2CRK h r SMB Read Block Data: read data from register\n\ +I2CRS h SMB Read Byte: read byte\n\ +I2CRW h r SMB Read Word Data: read word from register\n\ +I2CWB h r bv SMB Write Byte Data: write byte to register\n\ +I2CWD h bvs I2C write device\n\ +I2CWI h r bvs SMB Write I2C Block Data\n\ +I2CWK h r bvs SMB Write Block Data: write data to register\n\ +I2CWQ h bit SMB Write Quick: write bit\n\ +I2CWS h bv SMB Write Byte: write byte\n\ +I2CWW h r wv SMB Write Word Data: write word to register\n\ +I2CZ h bvs I2C zip\n\ +\n\ +LCFG Reload permits configuration file\n\ +LGV Get lg library version\n\ +\n\ +MICS v Microseconds delay\n\ +MILS v Milliseconds delay\n\ +\n\ +NC h Notification close\n\ +NO Notification open\n\ +NP h Notification pause\n\ +NR h Notification resume\n\ +\n\ +P h g pf pdc GPIO tx PWM (simple)\n\ +PARSE t Script validate\n\ +PCD Print daemon configuration directory\n\ +PROC t Script store\n\ +PROCD h Script delete\n\ +PROCP h Script get status and parameters\n\ +PROCR h pars Script run\n\ +PROCS h Script stop\n\ +PROCU h pars Script update parameters\n\ +PWD Print daemon working directory\n\ +PX h g pf pdc off cyc | GPIO tx PWM\n\ +\n\ +S h g spw GPIO tx servo pulses (simple)\n\ +SBC Get SBC's host name\n\ +SERC h Serial close device\n\ +SERDA h Serial data available\n\ +SERO dev b sef Serial open device\n\ +SERR h num Serial read bytes\n\ +SERRB h Serial read byte\n\ +SERW h bvs Serial write bytes\n\ +SERWB h bv Serial write byte\n\ +SHELL name str Execute a shell command\n\ +SPIC h SPI close device\n\ +SPIO spd spc b spf | SPI open device\n\ +SPIR h num SPI read bytes\n\ +SPIW h bvs SPI write bytes\n\ +SPIX h bvs SPI transfer bytes\n\ +SX h g sf spw off cyc | GPIO tx servo pulses\n\ +SHARE Set share\n\ +\n\ +T Get nanoseconds since the epoch\n\ +TICK Get nanoseconds since the epoch\n\ +\n\ +U Set user\n\ +USER Set user\n\ +\n\ +Numbers may be entered as hex (prefix 0x), octal (prefix 0),\n\ +otherwise they are assumed to be decimal.\n\ +\n\ +Examples\n\ +\n\ +rgs u test1 s 1 i2co 1 0x20 0 # get handle to device 0x20 on I2C bus 1\n\ +\n\ +man rgs for full details.\n\ +\n"; + +static uint64_t xMakeSalt(void) +{ + struct timespec xts; + + clock_gettime(CLOCK_REALTIME, &xts); + + return ((xts.tv_sec + xts.tv_nsec) * random()) + (random() + xts.tv_nsec); +} + +static void xReport(int err, char *fmt, ...) +{ + char buf[128]; + va_list ap; + + if (err > status) status = err; + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + + fprintf(stderr, "%s\n", buf); + + fflush(stderr); +} + +static int xInitOpts(int argc, char *argv[]) +{ + int opt, args; + + opterr = 0; + + args = 1; + + while ((opt = getopt(argc, argv, "ahvx")) != -1) + { + switch (opt) + { + case 'a': + printFlags |= PRINT_ASCII; + args++; + break; + + case 'h': + printf("%s", xCmdUsage); + args++; + break; + + case 'x': + printFlags |= PRINT_HEX; + args++; + break; + + case 'v': + printf("rgs_%d.%d.%d.%d\n", + (RGS_VERSION>>24)&0xff, (RGS_VERSION>>16)&0xff, + (RGS_VERSION>>8)&0xff, RGS_VERSION&0xff); + args++; + break; + + default: + args++; + xReport(RGS_OPTION_ERR, "ERROR: bad option %c", optopt); + } + } + return args; +} + +static int xOpenSocket(void) +{ + int sock, err; + struct addrinfo hints, *res, *rp; + const char *addrStr, *portStr; + + portStr = getenv(LG_ENVPORT); + + if (!portStr) portStr = LG_DEFAULT_SOCKET_PORT_STR; + + addrStr = getenv(LG_ENVADDR); + + if (!addrStr) addrStr = LG_DEFAULT_SOCKET_ADDR_STR; + + memset (&hints, 0, sizeof (hints)); + + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags |= AI_CANONNAME; + + err = getaddrinfo(addrStr, portStr, &hints, &res); + + if (err) return SOCKET_OPEN_FAILED; + + for (rp=res; rp!=NULL; rp=rp->ai_next) + { + sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + + if (sock == -1) continue; + + if (connect(sock, rp->ai_addr, rp->ai_addrlen) != -1) break; + } + + freeaddrinfo(res); + + if (rp == NULL) return SOCKET_OPEN_FAILED; + + return sock; +} + +static void xShowResult(int rv, lgCmd_p cmdP, char *cmdExt) +{ + int i, r, ch; + uint32_t *argI=(uint32_t*)&cmdP[1]; + uint64_t *argQ=(uint64_t*)&cmdP[1]; + + r = cmdP->status; + + switch (rv) + { + case 0: + case 1: + if (r < 0) + { + printf("%d\n", r); + xReport(RGS_SCRIPT_ERR, "ERROR: %s", lgErrStr(r)); + } + break; + + case 2: + printf("%d\n", r); + if (r < 0) xReport(RGS_SCRIPT_ERR, "ERROR: %s", lgErrStr(r)); + break; + + case 3: + if (r < 0) + { + printf("%d\n", r); + xReport(RGS_SCRIPT_ERR, "ERROR: %s", lgErrStr(r)); + } + else printf("%"PRIu64"\n", argQ[0]); + break; + + case 5: + printf("lg_%d.%d.%d.%d\n", + (cmdP->status>>24)&0xff, (cmdP->status>>16)&0xff, + (cmdP->status>>8)&0xff, cmdP->status&0xff); + break; + + case 6: /* + BI2CZ CF2 FL FR I2CPK I2CRD I2CRI I2CRK + I2CZ SBC SERR SLR SPIX SPIR USER + */ + + if ((cmdP->cmd == LG_CMD_PCD) || + (cmdP->cmd == LG_CMD_PWD) || + (cmdP->cmd == LG_CMD_SBC)) + { + printf("%s\n", cmdExt); + break; + } + + printf("%d", r); + if (r < 0) xReport(RGS_SCRIPT_ERR, "ERROR: %s", lgErrStr(r)); + if (r > 0) + { + if (printFlags == PRINT_ASCII) printf(" "); + + for (i=0; icmd, r); + if (r < 0) xReport(RGS_SCRIPT_ERR, "ERROR: %s", lgErrStr(r)); + break; + + } +} + +static int xSendCommand(int sock, lgCmd_p cmdP, char *cmdExt) +{ + if (sock == SOCKET_OPEN_FAILED) + { + xReport(RGS_CONNECT_ERR, "socket connect failed"); + return -1; + } + + if (send(sock, cmdP, sizeof(lgCmd_t), 0) != sizeof(lgCmd_t)) + { + xReport(RGS_CONNECT_ERR, "socket send failed"); + return -1; + } + + if (cmdP->size) + { + if (send(sock, cmdExt, cmdP->size, 0) != cmdP->size) + { + xReport(RGS_CONNECT_ERR, "socket send failed"); + return -1; + } + } + + if (recv(sock, cmdP, sizeof(lgCmd_t), MSG_WAITALL) != + sizeof(lgCmd_t)) + { + xReport(RGS_CONNECT_ERR, "socket receive failed"); + return -1; + } + + if (cmdP->size) + { + if (recv(sock, cmdExt, cmdP->size, MSG_WAITALL) != + cmdP->size) + { + xReport(RGS_CONNECT_ERR, "socket receive failed"); + return -1; + } + cmdExt[cmdP->size] = 0; + } + return 0; +} + +int main(int argc , char *argv[]) +{ + int sock; + int args, idx, i, pp, l, len; + char salt1[LG_SALT_LEN]; + char user[LG_USER_LEN]; + cmdCtl_t ctl; + cmdScript_t s; + lgCmd_t cmdBuf[CMD_MAX_EXTENSION/sizeof(lgCmd_t)]; + lgCmd_p cmdP=cmdBuf; + char *cmdExt=(char*)&cmdP[1]; + + sock = xOpenSocket(); + + args = xInitOpts(argc, argv); + + text[0] = 0; + l = 0; + pp = 0; + + for (i=args; i= 0) && (ctl.eaten < len)) + { + if ((idx=cmdParse(text, &ctl, cmdBuf, sizeof(cmdBuf))) >= 0) + { + cmdP->magic = LG_MAGIC; + cmdP->doubles = 0; + cmdP->longs = 0; + cmdP->shorts = 0; + + if (cmdP->cmd < LG_CMD_SCRIPT) + { + if (cmdP->cmd == LG_CMD_PARSE) + { + cmdParseScript(cmdExt, &s, 1); + if (s.par) free (s.par); + } + else if (cmdP->cmd == LG_CMD_USER) + { + /* need to auto login, create salt */ + snprintf(salt1, LG_SALT_LEN, "%015"PRIx64, xMakeSalt()); + /* save username */ + snprintf(user, LG_USER_LEN, "%s", cmdExt); + /* change command to salt + user */ + sprintf(cmdExt, "%s.%s", salt1, user); + cmdP->size = strlen(cmdExt); + + if (xSendCommand(sock, cmdP, cmdExt) == 0) + { + /* take salt2 from message and overwrite with hash */ + lgMd5UserHash(user, salt1, cmdExt, "", cmdExt); + cmdP->cmd = LG_CMD_PASSW; + cmdP->size = strlen(cmdExt); + if (xSendCommand(sock, cmdP, cmdExt) == 0) + xShowResult(0, cmdP, cmdExt); + } + } + else + { + if (xSendCommand(sock, cmdP, cmdExt) == 0) + xShowResult(cmdInfo[idx].rv, cmdP, cmdExt); + } + } + else xReport(RGS_SCRIPT_ERR, + "%s only allowed within a script", cmdInfo[idx].name); + } + else + { + if (idx == CMD_UNKNOWN_CMD) + xReport(RGS_SCRIPT_ERR, + "%s? unknown command, rgs -h for help", cmdStr()); + else + xReport(RGS_SCRIPT_ERR, + "%s: bad parameter, rgs -h for help", cmdStr()); + } + } + + if (sock >= 0) close(sock); + + return status; +} +

5F6aiVvkc6=aqWC$okg#~GuRy)el*cE+B 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 ZdKsPD^s|<@^X2S1G7<_;K8xx5{vUv+4O;*J literal 0 HcmV?d00001 diff --git a/DOC/HTML/images/LDR-gnup-2.png b/DOC/HTML/images/LDR-gnup-2.png new file mode 100644 index 0000000000000000000000000000000000000000..44530965f27181a0d357ae1b7398e91dd1bda912 GIT binary patch literal 31410 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 Gum2CWU|Svl literal 0 HcmV?d00001 diff --git a/DOC/HTML/images/LDR-photo.jpg b/DOC/HTML/images/LDR-photo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..95a63a3081090ae2d24be52b4fdbfed6b6cff3e0 GIT binary patch literal 109499 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�OWbEY1%xj*7m^L){y612ngfT6y*aX25#kN;7u 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 literal 0 HcmV?d00001 diff --git a/DOC/HTML/images/LDR-wave-1.png b/DOC/HTML/images/LDR-wave-1.png new file mode 100644 index 0000000000000000000000000000000000000000..69bb43b5f58cb31127013818f2698fe8f4656bba GIT binary patch literal 4557 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 Q6BrPnrGZp0g;M1& literal 0 HcmV?d00001 diff --git a/DOC/HTML/images/LDR-wave-3.png b/DOC/HTML/images/LDR-wave-3.png new file mode 100644 index 0000000000000000000000000000000000000000..a2ca42a329555650350804a30c190c722dc3d944 GIT binary patch literal 4872 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 literal 0 HcmV?d00001 diff --git a/DOC/HTML/images/breadboard.jpg b/DOC/HTML/images/breadboard.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5d916f70e9b5815035b85516b8552a104d3cb6bf GIT binary patch literal 46953 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") + else: + in_list = True + if man: + pass + else: + emit("
    ") + continue + + if at != NONE: + if in_list: + if man: + emit(".br\no "+line) + else: + emit("
  • "+line+"
  • ") + continue + + if line.find("@") != -1: + if not in_table: + in_table = True + + if man: + pass + else: + emit("") + + if man: + line = line.replace("@", " ") + emit(line) + # emit("\n.br\n") + emit(".br\n") + else: + emit("") + cols = line.split("@") + for col in cols: + emit("".format(col.strip())) + emit("") + + continue + + else: + if in_table: + in_table = False + + if man: + pass + else: + emit("
    {}
    ") + + if line == "...\n" or line == ". .\n": + if in_code: + in_code = False + + if man: + emit("\n.EE\n") + else: + emit("
    ") + + else: + in_code = True + if line == "...\n": + + if man: + emit("\\fBExample\\fP\n.br\n") + else: + emit("Example

    ") + + if man: + emit("\n.EX\n") + else: + emit("") + + continue + + if line == "\n": + + if man: + emit("\n.br\n") + else: + emit("
    ") + + if not in_code: + if man: + emit("\n.br\n") + else: + emit("
    ") + + continue + + + if in_code: + + if man: + line = line.replace("\n", "\n.br\n") + else: + line = line.replace(" ", " ") + line = line.replace("\n", "
    ") + + if at == TEXT: + emit(line) + + elif at == MAN: + if man: + emit(line) + + elif at == OPT: + line = line.split("|") + if man: + emit("\n.IP \"\\fB{}\\fP\"\n{}.".format(line[0], line[1])) + else: + emit("{}{}". + format(line[0], line[1])) + + elif at == DEFS: + line = line.replace("#define ", "") + line = line.replace("#define ", "") + emit(line) + +if man: + if obj == "rgpiod": + emit(rgpiod_m2) + +f.close() + diff --git a/DOC/bin/examples.py b/DOC/bin/examples.py new file mode 100755 index 0000000..e389341 --- /dev/null +++ b/DOC/bin/examples.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 + +import sys +from collections import OrderedDict + +def emit(s): + sys.stdout.write(s) + +def get_line(f): + line = f.readline() + return line + +def get_file(): + try: + fn = sys.argv[1] + f = open(fn, "r") + except: + exit("aborting, can't open {}".format(fn)) + return f + +def cancel_row(): + global in_row + if in_row: + in_row = False + emit('') + +def start_row(): + global in_row + if not in_row: + in_row = True + emit('') + +def cancel_table(): + global in_table + if in_table: + in_table = False + cancel_row() + emit('') + +def start_table(): + global in_table + if not in_table: + in_table = True + emit('') + else: + cancel_row() + start_row() + +index = {} + +in_code = False +in_samp = False +in_table = False +in_row = False + +f = get_file() + +while True: + + line = get_line(f) + + if line == "": + cancel_table() + + emit('
    \n') + last = None + ordered = OrderedDict(sorted(index.items(), key=lambda t: t[1].lower())) + for k,v in ordered.items(): + tag=k.split('_')[0] + if last != v.lower(): + if last is not None: + emit('') + last = v.lower() + anchor="index_"+last.replace(" ", "_") + emit('
    '+v+'') + emit(' '+tag+'\n') + emit('
    ') + break + + if line.startswith("?0|"): + s = line.split("|") + anchor=s[1].strip() + emit(''+anchor+'
    ') + continue + + if line.startswith("?1|"): + cancel_table() + s = line.split("|") + tag = s[1].strip()+"_" + anchor=s[2].strip() + emit('

    '+anchor+'

    ') + continue + + elif line.startswith("?2|") or line.startswith("?3|") or line.startswith("?4|"): + start_table() + + s = line.split("|") + + anchor=tag+s[1].strip() + + if line.startswith("?2|"): + link=s[1].strip()+".html" + elif line.startswith("?3|"): + link="code/"+s[1].strip()+".zip" + elif line.startswith("?4|"): + link=s[1].strip() + else: + link="code/"+s[1].strip() + + date=s[2].strip() + + title=s[3].strip() + emit(''+title+'
    '+date+'
    ') + + index.update({anchor:title}) + + continue + + else: + emit(line.replace("\n", "
    \n")) + +f.close() + diff --git a/DOC/bin/html.py b/DOC/bin/html.py new file mode 100755 index 0000000..9ff6e29 --- /dev/null +++ b/DOC/bin/html.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python3 + +import sys +import sqlite3 +import time + +i_file_name = 0 +i_menu_title = 1 +i_menu_pos = 2 +i_menu_level = 3 +i_page_title = 4 +i_pic1 = 5 +i_pic2 = 6 +i_pic3 = 7 +i_body = 8 + +print(""" + + + + + + + lg library + + + + + +""".format(time.time())) + +page = sys.argv[1] + +menuH = "" +menuV = "" +sitemap = "" + +header = ' lg archive' + +footer1 = "© 2020-2020"; +footer2 = ""; +footer3 = " : " + time.strftime("%d/%m/%Y") + ""; + +db=sqlite3.connect("dbase/lg.sqlite") + +c=db.cursor() + +def menu_titles(): + + global menuV, menuH + + c.execute( + "SELECT file_name, menu_title, menu_level FROM lg ORDER by menu_pos") + + recs = c.fetchall() + + menuV = "" + menuH = "" + + for r in recs: + if r[2] == 1: + menuV += '' + r[1] + '\n' + menuH += '[' + r[1] + ']\n' + +def sitemap(): + + c.execute( + "SELECT file_name, menu_title, menu_level FROM lg ORDER by menu_pos") + + recs = c.fetchall() + + stemap = "" + + for r in recs: + if r[2] > 0: + s = "----" * (r[2]-1) + stemap += s + '' + r[1] + '
    \n' + + return stemap + +def check_image(d): + + img = "images/" + d + + try: + with open("HTML/" + img) as f: + #print('') + print('') + except: + pass + +titles = menu_titles() + +s_sidebar = 'style="background:#EAF2E6 url(\'images/sidebar.gif\') repeat-y; width:35px; height:100%"' + +s_header = 'style="background:url(\'images/topbar.gif\') repeat-x; height: 70px; font-size:1.5em; vertical-align: top;"' + +s_menuV = 'style="vertical-align: top; background-color: #98bf21;"' + +c.execute("SELECT * FROM lg WHERE file_name=?", (page,)) + +rec = c.fetchone() + +if page == "sitemap": + body = sitemap() +else: + body = rec[i_body] + +c.close() +db.close() + +#print('') +print('
    ') +print('') +print('') +print('
    ') +print('') +print('
    ' + header + '
    ') +print('
    ') +print('
    ') +check_image(rec[i_pic1]) +check_image(rec[i_pic2]) +check_image(rec[i_pic3]) +print('
    ') +print("") +print('') +print('') +print('
    ' + menuV + '

    ' + rec[i_page_title] + '

    ' + body + '
    ') +print('
    ' + menuH + '
    ') +print('') +print('') +print('') +print('') +print('
    ' + footer1 + '
    ' + footer2 + '
    ' + footer3 + '
    ') +print('
    ') + +print('\n') + diff --git a/DOC/bin/purge.sh b/DOC/bin/purge.sh new file mode 100755 index 0000000..b50875a --- /dev/null +++ b/DOC/bin/purge.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +# if backup same as new delete it +if cmp -s dbase/lg.sqlite dbase/lg.sqlite.bak +then + rm dbase/lg.sqlite.bak +else + d=$(date "+%F-%H-%M-%S") + mv -f dbase/lg.sqlite.bak dbase/lg.sqlite.$d +fi + +# delete backups older than a week +find dbase/lg.sqlite.2* -mtime +7 -delete &>/dev/null + diff --git a/DOC/bin/pymakdoc.py b/DOC/bin/pymakdoc.py new file mode 100755 index 0000000..d134367 --- /dev/null +++ b/DOC/bin/pymakdoc.py @@ -0,0 +1,314 @@ +#!/usr/bin/env python3 + +import sys + +def emit(s): + sys.stdout.write(s) + +def str2hex(s): + return ":".join("{:02x}".format(ord(c)) for c in s) + +def funchdr(line): + if len(line) > 1 and (not line.startswith(" ")) and line.find("(") != -1: + return True + else: + return False + +def get_line(f): + line = f.readline() + line = line.replace("<", "<") + line = line.replace(">", ">") + return line + +def get_file(): + try: + fn = sys.argv[1] + f = open(fn, "r") + except: + exit("aborting, can't open {}".format(fn)) + return f + +NONE = 0 +DESCRIPTION = 1 +OVERVIEW = 2 +CLASSES = 3 +CLASS = 4 +FUNC = 5 +XREF = 6 +DATA = 7 + +if len(sys.argv) > 2: + lgpio = True +else: + lgpio = False + +f = get_file() + +param_used = [] +param_defd = [] +param_refd = [] + +at = NONE + +in_code = False +in_samp = False +in_table = False +in_list = False + +left = 0 + +while True: + + line = get_line(f) + + if line == "": + for p in param_used: + if p not in param_defd: + sys.stderr.write("{} is used but not defined.\n".format(p)) + for p in param_defd: + if p not in param_used and p not in param_refd: + sys.stderr.write("{} is defined but not used.\n".format(p)) + break + + if line.startswith(" o "): + if not in_list: + in_list = True + emit("
      ") + emit("
    • "+ line[6:] +"
    • ") + continue + + if in_list: + in_list = False + emit("
    ") + + if line.startswith("DESCRIPTION"): + left = 4 + at = DESCRIPTION + continue + + elif line.startswith(" OVERVIEW"): + left = 4 + emit("

    OVERVIEW

    ") + emit( + "") + at = OVERVIEW + continue + + elif line.startswith("CLASSES"): + left = 4 + emit("
    ") + at = NONE + continue + + elif line.startswith(" | -----"): + in_func = False + at = NONE + continue + + elif line.startswith(" class "): + in_func = False + if line.startswith(" class error"): + at = NONE + else: + left = 8 + (b, s, e) = line.partition('(') + emit("

    {}

    ".format(b)) + _class = b[10:] + at = CLASS + continue + + elif line.startswith("FUNCTIONS"): + left = 4 + emit("

    FUNCTIONS

    ") + in_func = False + at = FUNC + continue + + elif line.startswith(" xref()"): + left = 8 + emit("

    PARAMETERS

    ") + in_func = False + last_par = "" + at = XREF + continue + + elif line.startswith("DATA"): + at = DATA + continue + + line = line[left:] + + while line.find('[*') != -1 and line.find('*]') != -1: + (b, s, e) = line.partition('[*') + (l, s, e) = e.partition('*]') + line = '{}{}{}'.format(b, l, l, e) + if l not in param_refd: + param_refd.append(l) + + while line.find('[[') != -1 and line.find(']]') != -1: + (b, s, e) = line.partition('[[') + (l, s, e) = e.partition(']]') + line = '{}{}{}'.format(b, l, l, e) + + while line.find("[+") != -1 and line.find("+]") != -1: + (b, s, e) = line.partition("[+") + (l, s, e) = e.partition("+]") + + line = "{}{}{}".format(b, l.lower(), l, e) + + if len(line) > 0: + if line[0] == '*' and line[-2] == '*': + line = "

    {}

    ".format(line[1:-2]) + + if line[0] == '[' and line[-2] == ']': + pass + + at_funchdr = funchdr(line) + + if at != NONE and at != OVERVIEW: + + if at == CLASS or at == FUNC: + + #if not at_funchdr and at == FUNC: + if not at_funchdr: + line = line[4:] + + line = line.replace(' \n', '
    ') + + if line.find('@') != -1: + if not in_table: + in_table = True + emit("") + emit("") + cols = line.split('@') + for col in cols: + emit("".format(col.strip())) + emit("") + continue + else: + if in_table: + in_table = False + emit("
    {}
    ") + + if line.find(":=") != -1: + if not in_samp: + emit("
    Parameters

    ") + in_samp = True + + if line == '...\n' or line == '. .\n': + if in_code: + in_code = False + emit("
    ") + else: + in_code = True + if line == '...\n': + emit("Example

    ") + emit("") + continue + + if line == '\n' or line == '': + if in_samp: + emit("") + in_samp = False + emit('
    ') + if not in_code: + emit('
    ') + continue + + if in_code or in_samp: + line = line.replace(" ", " ") + line = line.replace("") + + if at == DESCRIPTION: + emit(line) + + elif at == OVERVIEW: + if line == "\n": + emit("") + else: + (func, sep, desc) = line.partition(' ') + if desc != '': + emit("{}{}". + format(func, func, desc)) + else: + emit("{}". + format(func).replace("_", " ")) + + elif at == CLASS or at == FUNC: + if at_funchdr: + in_func = True + + if in_code: + emit("
    ***C***
    ") + in_code = False + + if in_samp: + emit("
    ***S***") + in_samp = False + + (func, sep1, end1) = line.partition("(") + (parl, sep2, end2) = end1.partition(")") + pars = parl.split(",") + + func = func.strip() + + if at == FUNC: + if not lgpio: + func = "rgpio." + func + else: + if func == "__init__": + if not lgpio: + func = "rgpio." + _class + else: + func = _class + + emit("

    {}".format(func,func)) + + emit('(') + + x = 0 + for p in pars: + (p, sep3, end3) = p.partition('=') + p = p.strip() + end3 = end3.strip() + if len(end3): + end3 = "=" + end3 + if end3 == "=18446744073709551615": + end3 = "=GROUP_ALL" + if end3.endswith("/.lg_secret'"): + end3 = "='~/.lg_secret'" + if (p != 'self') and (p != '...') and (p != ''): + if p not in param_used: + param_used.append(p) + if x > 0: + emit(", ") + x += 1 + emit("{}".format(p, p+end3)) + emit(')

    \n') + + else: + if in_func: + emit(line) + + elif at == XREF: + if line.find(':') != -1: + (par, sep, end) = line.partition(':') + par = par.strip() + end = end.strip() + emit("

    {}: {}

    ".format(par, par, end)) + + if par.lower() < last_par.lower(): + sys.stderr.write("Out of order {} after {}.\n". + format(par, last_par)) + last_par = par + + if par in param_defd: + sys.stderr.write("Duplicate definition of {}.\n".format(par)) + else: + param_defd.append(par) + else: + emit(line) + +f.close() + diff --git a/DOC/bin/smakdoc.py b/DOC/bin/smakdoc.py new file mode 100755 index 0000000..52cbc92 --- /dev/null +++ b/DOC/bin/smakdoc.py @@ -0,0 +1,380 @@ +#!/usr/bin/env python3 + +import sys + +def get_file(): + try: + fn = sys.argv[1] + f = open(fn, "r") + except: + exit("aborting, can't open {}".format(fn)) + return f + +def emit(s): + sys.stdout.write(s) + +def str2hex(s): + return ":".join("{:02x}".format(ord(c)) for c in s) + +def get_line(f): + line = f.readline() + if man: + line = line.replace(" \n", "\n.br\n") + else: + line = line.replace("<", "<") + line = line.replace(">", ">") + line = line.replace(" \n", "
    \n") + return line + +if len(sys.argv) > 2: # Are we creating a man page? + man = True +else: + man = False + +NONE=0 +INTRO=1 +OVERVIEW=2 +COMMANDS=3 +PARAMETERS=4 + +param_used = [] +param_defd = [] + +f = get_file() + +at = NONE + +in_table = False +in_code = False +in_list = False + +funcdef ={} + +if man: + emit(""" +.\" Process this file with +.\" groff -man -Tascii foo.1 +.\" +.TH rgs 1 2020-2020 Linux "lg archive" +.SH NAME +rgs - a shell command to manipulate a remote SBC's GPIO.\n +.SH SYNOPSIS\n +.B rgpiod &\n +then\n +.B rgs {command}+\n +.SH DESCRIPTION\n +.ad l\n +.nh\n +""") + +while True: + + line = get_line(f) + + if line == "": + for p in param_used: + if p not in param_defd: + sys.stderr.write("{} used but not defined.\n".format(p)) + for p in param_defd: + if p not in param_used: + sys.stderr.write("{} defined but not used.\n".format(p)) + break + + while line.find("[*") != -1 and line.find("*]") != -1: + (b, s, e) = line.partition("[*") + (l, s, e) = e.partition("*]") + if man: + line = "{}\\fB{}\\fP{}".format(b, l, e) + else: + line = "{}{}{}".format(b, l, l, e) + + while line.find("[{") != -1 and line.find("}]") != -1: + (b, s, e) = line.partition("[[") + (l, s, e) = e.partition("]]") + + if man: + line = "{}\\fB{}\\fP{}".format(b, l, e) + else: + line = "{}{}{}".format(b, l, l, e) + + while line.find("[+") != -1 and line.find("+]") != -1: + (b, s, e) = line.partition("[+") + (l, s, e) = e.partition("+]") + + if man: + line = "{}\\fB{}\\fP{}".format(b, l, e) + else: + line = "{}{}{}".format(b, l.lower(), l, e) + + while line.find("[#") != -1 and line.find("#]") != -1: + (b, s, e) = line.partition("[#") + (l, s, e) = e.partition("#]") + + if man: + line = "{}\\fB{}\\fP{}".format(b, l, e) + else: + line = "{}{}{}".format(b, l, e) + + if line[0] == "*" and line[-2] == "*": + if man: + line = ".SS {}".format(line[1:-2]) + else: + line = "

    {}

    ".format(line[1:-2]) + + if line.startswith("o "): + if not in_list: + in_list = True + if not man: + emit("
      ") + if man: + emit("\n.br\n"+line) + else: + emit("
    • "+ line[2:] +"
    • ") + continue + + if in_list: + in_list = False + if not man: + emit("
    ") + + if line.startswith("INTRO"): + line = get_line(f) + + if man: + pass + else: + emit("

    Introduction

    \n") + + at = INTRO + + elif line.startswith("OVERVIEW"): + line = get_line(f) + + if man: + emit("\n.SH OVERVIEW\n") + else: + emit("

    Overview

    \n") + emit( + "") + at = OVERVIEW + + elif line.startswith("COMMANDS"): + line = get_line(f) + + if man: + emit("\n.SH COMMANDS\n") + else: + emit("
    ") + emit("

    Commands

    \n") + + funcs = sorted(funcdef) + at = COMMANDS + + elif line.startswith("PARAMETERS"): + line = get_line(f) + last_par = "" + + if man: + emit("\n.SH PARAMETERS\n") + else: + emit("

    Parameters

    \n") + + at = PARAMETERS + + if at != NONE: + if line.find("@") != -1: + if not in_table: + in_table = True + + if man: + emit("\n.EX\n") + else: + emit("") + if man: + pass + else: + emit("") + + if man: + line = line.replace("@", " ") + emit(line) + else: + cols = line.split("@") + for col in cols: + emit("".format(col.strip())) + + if man: + pass + else: + emit("") + continue + else: + if in_table: + in_table = False + + if man: + emit("\n.EE\n") + else: + emit("
    {}
    ") + + if line == "...\n" or line == ". .\n": + if in_code: + in_code = False + + if man: + emit("\n.EE\n") + else: + emit("") + + else: + in_code = True + + if at == COMMANDS: + + if line == "...\n": + if man: + emit("\n\\fBExample\\fP\n.br\n") + else: + emit("Example

    ") + + if man: + emit("\n.EX\n") + else: + emit("") + + continue + + if line == "\n" and at != OVERVIEW: + if man: + # emit("\n.br\n.br\n") + emit("\n.br\n") + else: + emit("

    ") + continue + + if in_code: + if man: + line = line.replace("\n", "\n.br\n") + else: + line = line.replace(" ", " ") + line = line.replace("\n", "
    ") + + + if at == INTRO: + emit(line) + + elif at == OVERVIEW: + if line == "\n": + + if man: + pass + else: + emit("") + + elif line.find("::") == -1: + + if man: + emit(".SS {}".format(line)) + else: + emit("{}".format(line)) + else: + (funcpar, sep, desc) = line.partition("::") + funcpar = funcpar.strip() + desc = desc.strip() + if desc != "": + (func, sep, parl) = funcpar.partition(" ") + func = func.strip() + parl = parl.strip() + if func in funcdef: + sys.stderr.write("{} has been redefined.\n".format(func)) + funcdef[func]=parl+"::"+desc + + if man: + emit(".B {}\n".format(func+" "+parl+" ")) + pass + else: + emit("{}".format(func, func)) + + pars = parl.split() + + for p in pars: + + if p not in param_used: + param_used.append(p) + + if man: + pass + else: + emit(" {}".format(p, p)) + + if man: + emit("{}\n.P\n".format(desc)) + pass + else: + emit("{}".format(desc)) + + elif at == COMMANDS: + if line.find("::") != -1: + (func, sep, desc) = line.partition("::") + func = func.strip() + + if func not in funcdef: + parl="unknown" + desc="unknown" + else: + (parl,sep,desc) = funcdef[func].partition("::") + + (des, sep, c) = desc.partition("::") + + if man: + t = "\\fB" + func + " " + parl + "\\fP" + " - " + des.strip() + emit("\n.IP \"{}\"\n.IP \"\" 4\n".format(t)) + else: + emit("

    {}\n".format(func,func)) + + pars = parl.split() + for p in pars: + + if man: + pass + else: + emit(" {}".format(p, p)) + + if man: + pass + else: + emit(" - {}

    ".format(des.strip())) + + else: + emit(line) + + elif at == PARAMETERS: + if line.find("::") != -1: + (par, sep, desc) = line.partition("::") + par = par.strip() + desc = desc.strip() + + if par.lower() < last_par.lower(): + sys.stderr.write("Out of order {} after {}.\n".format(par, last_par)) + last_par = par + + if par in param_defd: + sys.stderr.write("Duplicate definition of {}.\n".format(par)) + else: + param_defd.append(par) + + if man: + emit("\n.IP \"\\fB{}\\fP: {}\" 0\n".format(par, desc)) + else: + emit("

    {}: {}

    \n".format(par,par,desc)) + else: + emit(line) + +if man: + emit(""" +.SH SEE ALSO\n +rgpiod(1), lgpio(3), rgpio(3) +""") + +f.close() + diff --git a/DOC/bin/tidy.py b/DOC/bin/tidy.py new file mode 100755 index 0000000..4a789f4 --- /dev/null +++ b/DOC/bin/tidy.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 + +import glob + +def snafu(t, s, r): + l = t + while l.find(s) != -1: + l = l.replace(s, r) + return l + +for n in glob.glob("tmp/body/*.body"): + + f = open(n, "r"); + t = f.read() + f.close() + + t = snafu(t, "

    ", "

    ") + t = snafu(t, "

    ", "

    ") + t = snafu(t, "


    ", "