Browse Source

break up the README to an overview and the parts..

main
John-Mark Gurney 2 years ago
parent
commit
c5bb2d4d68
3 changed files with 358 additions and 285 deletions
  1. +293
    -0
      LORA-IRR.md
  2. +11
    -285
      README.md
  3. +54
    -0
      RS485HID.md

+ 293
- 0
LORA-IRR.md View File

@@ -0,0 +1,293 @@
LoRa Irrigation System
======================

This project is to build an irrigation system from LoRa capable
microcontrollers. The [Heltec Node151](https://heltec.org/project/lora-node-151/)
was chosen due to it's small size and inexpensive cost.

Design Decisions
----------------

While investigating this project, the LoraWAN protocol was investigated,
but after looking at the code complexity and other operational
requirements, if was decided that for this project, it was safer to
target a direct Node to Node style communication system. This would
allow the implementation to be more simple, and security to be built in
(LoRaWAN does have a crypto layer, BUT, trusting/auditing it and any
library that implements it would be a larger task than I want to
undertake). It could also be used for other projects that need
security.

One of the other requirements is that the code be 100% open sourced,
not GPL licensed, and no proprietary components. This meant that using
IDE's like ST's STM32CubeIDE which is only available in binary form
was not a choice, as that would preclude building on an operating
system other than Windows/MacOSX/Linux.

Architecture
------------

There are a number of components to make this system work. The overall
flow is:
<!-- If using MarkDeep, remove the triple backticks from this and the diagram below -->
```
************************************************************************************************
* *
* +---------+ +-------------+ +-------------+ +--------------+ *
* | lora.py | multicast | loraserv.py | USB VCP | lora.gw.elf | LoRa | lora.irr.elf | *
* | +--------------+ +------------+ main.c +---------+ irr_main.c | *
* +---------+ +-------------+ +-------------+ +--------------+ *
* *
************************************************************************************************
```

The `lora.py` component is the front end/UI that is used to send commands
to controller. This program establishes a secure communications channel
to the controller. The controller's firmware is in `lora.irr.elf`, and
the main source file is `irr_main.c`.

The middle to components, `loraserv.py` and `lora.gw.elf` are used to
pass messages between the former two. The `loraserv.py` program takes
multicast datagrams that are received on 239.192.76.111:21089, which
are with out any framing, prepends `pkt:`, hex encodes the data and
terminated w/ the new line character, and sends them via the USB VCP
provided by `lora.gw.elf`. The gateway firmware then decodes the
packet and transmits it via the LoRa radio to the irrigation
controller. Any received packet is returned similarly, but this time
with `data:` prepended, for `loraserv.py` to multicast back to
`lora.py`.

The reason no particular framing is required for addressing or
destination is that the protocol is secure, and only the party that
is able to decrypt the proper packets will be accepted, and any invalid
packets will be ignored.

Building
--------

The build system uses the BSD flavor of make. This is the default
make on the BSDs, originally called pmake, but also available as bsdmake
for MacOSX, and available as [bmake](https://crufty.net/help/sjg/bmake.html).

It also depends upon ARM's [GNU Arm Embedded
Toolchain](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm),
which uses gcc as the compiler. It would be good to get it
cross-compile with clang as well, but that requires finding a libc like
the nano libc that is provided by the toolchain.

One of the required parameters of the build is the shared key used for
authentication. A random key can be made using the command:
`make irrigation_key`, or it can be provided via the make command by
setting the variable IRR_KEY.

Note: Both IRR_KEY and the argument to `lora.py` will encode the
provided key to UTF-8.

Once ARM's toolchain is in your path, the following should work:
```
export MAKEOBJDIR=build
mkdir $MAKEOBJDIR
bsdmake all IRR_KEY=<sharedkey>
```

And in the directory `build`, two files, `lora.irr.elf` and
`lora.gw.elf` should be present. The file `lora.irr.elf` should be
flashed on the Node151 device that is used for interfacing to the
irrigation system as described in [Deploying](#deploying). The file
`lora.gw.elf` should be used on another Node151 that will be attached
to a computer used as the gateway which runs the `loraserv.py` software
as described in [Using](#using).

Flashing
--------

Flashing can be done via the open source tool
[OpenOCD](https://sourceforge.net/projects/openocd/). For this, I use
a Digilent HS1 JTAG programmer utilizing the [resistor
hack](https://github.com/ntfreak/openocd/blob/master/tcl/interface/ftdi/swd-resistor-hack.cfg)
to allow an FTDI JTAG programmer to control the bi-directional `SWIO`
pin.

One caveat w/ MacOSX, is that it may be necessary to unload the kext
`com.apple.driver.AppleUSBFTDI` via the command:
```
sudo kextunload -b com.apple.driver.AppleUSBFTDI
```
as OpenOCD wants direct access to the FTDI driver.

Once that happens, the device can be programmed using the following
command:
```
sudo openocd -f interface/ftdi/digilent-hs1.cfg -f interface/ftdi/swd-resistor-hack.cfg -f target/stm32l1.cfg -c "init" -c "reset init" -c "program build/lora.irr.elf verify reset exit"
```

Pins
----

The [pinout guide for the Node151](https://resource.heltec.cn/download/LoRa_Node_151/LoRa_Node_151_Pinout_Diagram.pdf).

The default pins PB5-7,9 are used as active low controls for the relays.
They are mapped to channels 0 through 3 respectively. The LED on PB8
is mapped to channel 4. This is useful for testing if a command works
or not.

The pin PB15 is used as an analog input for an RNG source. This pin
should be grounded.

Deploying
---------

Here is a diagram of the connections:

```
*******************************************************************************************
* *
* GND *
* +--------------------------------------------------+ *
* | | *
* +--------+ ~/~ +-+-----------+ GND +---------+ GPIO +-+--------------+ *
* | 24V AC +--------+ +----------------+ Node151 +---------+ Relay | *
* +--------+ | | +-+-------+ +-+--------------+ *
* | | +5V -> VUSB | | *
* | AC-DC PS 5V +------------------+ | +5V -> JD-VCC *
* | | | *
* | | | *
* | +--------------------------------------+ *
* +-------------+ *
* *
*******************************************************************************************
```

The normal supply used for irrigation values is 24V AC. This means
an additional power supply is needed to convert to the 5V supply that
is used by the Node151. Make sure this is well filtered as both the
relays on the board (talked about below), and the irrigation valves
will cause significant noise. The first PS I made was a simple DC-DC
buck converter + a full wave rectifier which, while alone was enough
to power the uC, was not enough when the relays were actuated, and even
when a little bit of filtering was added after the rectifier (22uF),
enough to keep it happy w/ the relays, it was not enough when the
irrigation valves actuated.

In order to control the values, a relay board, similar to [this
one](https://www.amazon.com/ELEGOO-Channel-Optocoupler-Arduino-Raspberry/dp/B01HEQF5HU),
can be used. Despite the GPIO on the Node151 being 3.3V, and the
relays requiring 5V, the jumper on the right side, VCC-JD-VCC, can be
removed to allow dual voltage operation. The GND on the input/VCC
pinout actually belongs to the relay power via JD-VCC, and NOT for the
VCC->INx pins, despite them being next to each other. The GND and
JD-VCC should be connected to the 5V power supply, while VCC is
connected to VDD on the Node151, and INx pins to the respective
GPIO pins.

Using
-----

The `lora.py` script requires at least Python 3.8. It also uses the
strobe library that in distributed in this repo. In general a
[virtualenv](https://virtualenv.pypa.io/en/latest/) is recommended for
all installed Python software to prevent version conflicts, but is not
always necessary. The `requirements.txt` file contains the necessary
modules to be installed, but simply adding the directory
`strobe/python` to PYTHONPATH should be sufficient.

The program `loraserv.py` takes a single argument, which is the device
file for the VCP that runs on the gateway. In my case, the device
name is `/dev/cu.usbmodem1451` as I am on my MacBook Pro, so the command
to launch the gateway is simply:
```
python3 loraserv.py /dev/cu.usbmodem1451
```

Note: On FreeBSD, the default open mode echos characters back to the
gateway causing it not to work. It is advised to setup the modem tty
via the command:
```
stty -f /dev/cuaU0.init gfmt1:cflag=cb00:iflag=2e02:lflag=43:oflag=2:discard=f:dsusp=19:eof=4:eol=ff:eol2=ff:erase=7f:erase2=8:intr=3:kill=15:lnext=16:min=1:quit=1c:reprint=12:start=11:status=14:stop=13:susp=1a:time=0:werase=17:ispeed=9600:ospeed=9600
```
before running the `loraserv.py` command. Replace `/dev/cuaU0.init`
with the respective device. The above was generated by running
`cu -l /dev/cuaU0` in one terminal, while obtaining the info via
`stty -g -f /dev/cuaU0` in another terminal.

Once that is running, then the `lora.py` program's multicast packets
will be forwarded out via the LoRa radio.

To test it, a simple `ping` command can be used, or turning on or off
the on board LED via channel 4 using the `setunset` command. The ping
command:
```
python3 lora.py -s <sharedkey> ping
```

To turn off the LED (which defaults to on):
```
python3 lora.py -s <sharedkey> setunset 4 0
```

Either of these commands should exit w/o message or error.

Multiple commands may be specified by separating them w/ `--` (two
hyphens). For example:
```
python3 lora.py -s <sharedkey> -- setunset 4 0 -- setunset 1 1
```

The first `--` is required to denote the end of option parsing,
otherwise the second `--` will be used, and the first `setunset`
command will be tried to be parsed as an argument.

If you have regular set of commands to run, they can be stored in a
file. Each line will be a single command, so you can have a file
similar to:
```
waitfor 5000
setunset 0 0
setunset 1 0
setunset 2 0
setunset 3 0
runfor 60000 0
waitfor 2000
runfor 60000 1
waitfor 2000
runfor 60000 2
waitfor 2000
runfor 60000 3
```

which will make sure all the valves are turned off, then run each one
in succession for 60 seconds, with a 2 second wait between.

There are two types of commands, ones that execute immediately, and ones
that are queued up for future execution. The immediate commands are:
* `adv`: Sets the current executing command to 0. An optional argument
specifies how many commands to advance by. Note that only the time
they are run is set to zero, so channels will be very briefly
activated. See the clear command to avoid this.
* `clear`: No argument, removes any future commands. Current command
remains executing. To clear all commands and stop all operations,
first do a `clear`, followed by an `adv`.
* `ping`: No argument, used to verify communication works.
* `setunset`: First argument is channel, second argument is 0 or 1,
specifying to turn the channel off (0) or on (1).

The following commands are queued. The first argument is the number
of milliseconds to run the command for before advancing to the next
command. The available commands are:
* `runfor`: And additional argument specifies the channel. The
channel will be set to on, and then when the command completes, The
channel will be set to off.
* `waitfor`: Wait for the specified time before advancing to the next
enqueued command.

Notes
-----

Significant portions of this code is copied from various reference
implementations. Basic review shows that it is of questionable quality.
It would be good to fully review all the code under stm32 and loramac
for any bugs and other issues. The buffer handling has already been
improved, but there is still plety of work that can be done to improve
it further.

<!-- Markdeep: --><style class="fallback">body{visibility:hidden;white-space:pre;font-family:monospace}</style><script src="markdeep.min.js" charset="utf-8"></script><script src="https://casual-effects.com/markdeep/latest/markdeep.min.js" charset="utf-8"></script><script>window.alreadyProcessedMarkdeep||(document.body.style.visibility="visible")</script>

+ 11
- 285
README.md View File

@@ -1,293 +1,19 @@
LoRa Irrigation System
======================
Syote: Secure IoT
=================

This project is to build an irrigation system from LoRa capable
microcontrollers. The [Heltec Node151](https://heltec.org/project/lora-node-151/)
was chosen due to it's small size and inexpensive cost.
This project started off as a project to control an [irrigation system
via a LoRa radio](LORA-IRR.md), but the core architecture of the
communication and security can easily be reused for other projects.

Design Decisions
----------------
The projects that make use of the core protocol are included in this
repo to make development easier for now, but if/when the project gets
large enough, they will be broken out into their own repositories.

While investigating this project, the LoraWAN protocol was investigated,
but after looking at the code complexity and other operational
requirements, if was decided that for this project, it was safer to
target a direct Node to Node style communication system. This would
allow the implementation to be more simple, and security to be built in
(LoRaWAN does have a crypto layer, BUT, trusting/auditing it and any
library that implements it would be a larger task than I want to
undertake). It could also be used for other projects that need
security.

One of the other requirements is that the code be 100% open sourced,
not GPL licensed, and no proprietary components. This meant that using
IDE's like ST's STM32CubeIDE which is only available in binary form
was not a choice, as that would preclude building on an operating
system other than Windows/MacOSX/Linux.

Architecture
------------

There are a number of components to make this system work. The overall
flow is:
<!-- If using MarkDeep, remove the triple backticks from this and the diagram below -->
```
************************************************************************************************
* *
* +---------+ +-------------+ +-------------+ +--------------+ *
* | lora.py | multicast | loraserv.py | USB VCP | lora.gw.elf | LoRa | lora.irr.elf | *
* | +--------------+ +------------+ main.c +---------+ irr_main.c | *
* +---------+ +-------------+ +-------------+ +--------------+ *
* *
************************************************************************************************
```

The `lora.py` component is the front end/UI that is used to send commands
to controller. This program establishes a secure communications channel
to the controller. The controller's firmware is in `lora.irr.elf`, and
the main source file is `irr_main.c`.

The middle to components, `loraserv.py` and `lora.gw.elf` are used to
pass messages between the former two. The `loraserv.py` program takes
multicast datagrams that are received on 239.192.76.111:21089, which
are with out any framing, prepends `pkt:`, hex encodes the data and
terminated w/ the new line character, and sends them via the USB VCP
provided by `lora.gw.elf`. The gateway firmware then decodes the
packet and transmits it via the LoRa radio to the irrigation
controller. Any received packet is returned similarly, but this time
with `data:` prepended, for `loraserv.py` to multicast back to
`lora.py`.

The reason no particular framing is required for addressing or
destination is that the protocol is secure, and only the party that
is able to decrypt the proper packets will be accepted, and any invalid
packets will be ignored.

Building
Projects
--------

The build system uses the BSD flavor of make. This is the default
make on the BSDs, originally called pmake, but also available as bsdmake
for MacOSX, and available as [bmake](https://crufty.net/help/sjg/bmake.html).

It also depends upon ARM's [GNU Arm Embedded
Toolchain](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm),
which uses gcc as the compiler. It would be good to get it
cross-compile with clang as well, but that requires finding a libc like
the nano libc that is provided by the toolchain.

One of the required parameters of the build is the shared key used for
authentication. A random key can be made using the command:
`make irrigation_key`, or it can be provided via the make command by
setting the variable IRR_KEY.

Note: Both IRR_KEY and the argument to `lora.py` will encode the
provided key to UTF-8.

Once ARM's toolchain is in your path, the following should work:
```
export MAKEOBJDIR=build
mkdir $MAKEOBJDIR
bsdmake all IRR_KEY=<sharedkey>
```

And in the directory `build`, two files, `lora.irr.elf` and
`lora.gw.elf` should be present. The file `lora.irr.elf` should be
flashed on the Node151 device that is used for interfacing to the
irrigation system as described in [Deploying](#deploying). The file
`lora.gw.elf` should be used on another Node151 that will be attached
to a computer used as the gateway which runs the `loraserv.py` software
as described in [Using](#using).

Flashing
--------

Flashing can be done via the open source tool
[OpenOCD](https://sourceforge.net/projects/openocd/). For this, I use
a Digilent HS1 JTAG programmer utilizing the [resistor
hack](https://github.com/ntfreak/openocd/blob/master/tcl/interface/ftdi/swd-resistor-hack.cfg)
to allow an FTDI JTAG programmer to control the bi-directional `SWIO`
pin.

One caveat w/ MacOSX, is that it may be necessary to unload the kext
`com.apple.driver.AppleUSBFTDI` via the command:
```
sudo kextunload -b com.apple.driver.AppleUSBFTDI
```
as OpenOCD wants direct access to the FTDI driver.

Once that happens, the device can be programmed using the following
command:
```
sudo openocd -f interface/ftdi/digilent-hs1.cfg -f interface/ftdi/swd-resistor-hack.cfg -f target/stm32l1.cfg -c "init" -c "reset init" -c "program build/lora.irr.elf verify reset exit"
```

Pins
----

The [pinout guide for the Node151](https://resource.heltec.cn/download/LoRa_Node_151/LoRa_Node_151_Pinout_Diagram.pdf).

The default pins PB5-7,9 are used as active low controls for the relays.
They are mapped to channels 0 through 3 respectively. The LED on PB8
is mapped to channel 4. This is useful for testing if a command works
or not.

The pin PB15 is used as an analog input for an RNG source. This pin
should be grounded.

Deploying
---------

Here is a diagram of the connections:

```
*******************************************************************************************
* *
* GND *
* +--------------------------------------------------+ *
* | | *
* +--------+ ~/~ +-+-----------+ GND +---------+ GPIO +-+--------------+ *
* | 24V AC +--------+ +----------------+ Node151 +---------+ Relay | *
* +--------+ | | +-+-------+ +-+--------------+ *
* | | +5V -> VUSB | | *
* | AC-DC PS 5V +------------------+ | +5V -> JD-VCC *
* | | | *
* | | | *
* | +--------------------------------------+ *
* +-------------+ *
* *
*******************************************************************************************
```

The normal supply used for irrigation values is 24V AC. This means
an additional power supply is needed to convert to the 5V supply that
is used by the Node151. Make sure this is well filtered as both the
relays on the board (talked about below), and the irrigation valves
will cause significant noise. The first PS I made was a simple DC-DC
buck converter + a full wave rectifier which, while alone was enough
to power the uC, was not enough when the relays were actuated, and even
when a little bit of filtering was added after the rectifier (22uF),
enough to keep it happy w/ the relays, it was not enough when the
irrigation valves actuated.

In order to control the values, a relay board, similar to [this
one](https://www.amazon.com/ELEGOO-Channel-Optocoupler-Arduino-Raspberry/dp/B01HEQF5HU),
can be used. Despite the GPIO on the Node151 being 3.3V, and the
relays requiring 5V, the jumper on the right side, VCC-JD-VCC, can be
removed to allow dual voltage operation. The GND on the input/VCC
pinout actually belongs to the relay power via JD-VCC, and NOT for the
VCC->INx pins, despite them being next to each other. The GND and
JD-VCC should be connected to the 5V power supply, while VCC is
connected to VDD on the Node151, and INx pins to the respective
GPIO pins.

Using
-----

The `lora.py` script requires at least Python 3.8. It also uses the
strobe library that in distributed in this repo. In general a
[virtualenv](https://virtualenv.pypa.io/en/latest/) is recommended for
all installed Python software to prevent version conflicts, but is not
always necessary. The `requirements.txt` file contains the necessary
modules to be installed, but simply adding the directory
`strobe/python` to PYTHONPATH should be sufficient.

The program `loraserv.py` takes a single argument, which is the device
file for the VCP that runs on the gateway. In my case, the device
name is `/dev/cu.usbmodem1451` as I am on my MacBook Pro, so the command
to launch the gateway is simply:
```
python3 loraserv.py /dev/cu.usbmodem1451
```

Note: On FreeBSD, the default open mode echos characters back to the
gateway causing it not to work. It is advised to setup the modem tty
via the command:
```
stty -f /dev/cuaU0.init gfmt1:cflag=cb00:iflag=2e02:lflag=43:oflag=2:discard=f:dsusp=19:eof=4:eol=ff:eol2=ff:erase=7f:erase2=8:intr=3:kill=15:lnext=16:min=1:quit=1c:reprint=12:start=11:status=14:stop=13:susp=1a:time=0:werase=17:ispeed=9600:ospeed=9600
```
before running the `loraserv.py` command. Replace `/dev/cuaU0.init`
with the respective device. The above was generated by running
`cu -l /dev/cuaU0` in one terminal, while obtaining the info via
`stty -g -f /dev/cuaU0` in another terminal.

Once that is running, then the `lora.py` program's multicast packets
will be forwarded out via the LoRa radio.

To test it, a simple `ping` command can be used, or turning on or off
the on board LED via channel 4 using the `setunset` command. The ping
command:
```
python3 lora.py -s <sharedkey> ping
```

To turn off the LED (which defaults to on):
```
python3 lora.py -s <sharedkey> setunset 4 0
```

Either of these commands should exit w/o message or error.

Multiple commands may be specified by separating them w/ `--` (two
hyphens). For example:
```
python3 lora.py -s <sharedkey> -- setunset 4 0 -- setunset 1 1
```

The first `--` is required to denote the end of option parsing,
otherwise the second `--` will be used, and the first `setunset`
command will be tried to be parsed as an argument.

If you have regular set of commands to run, they can be stored in a
file. Each line will be a single command, so you can have a file
similar to:
```
waitfor 5000
setunset 0 0
setunset 1 0
setunset 2 0
setunset 3 0
runfor 60000 0
waitfor 2000
runfor 60000 1
waitfor 2000
runfor 60000 2
waitfor 2000
runfor 60000 3
```

which will make sure all the valves are turned off, then run each one
in succession for 60 seconds, with a 2 second wait between.

There are two types of commands, ones that execute immediately, and ones
that are queued up for future execution. The immediate commands are:
* `adv`: Sets the current executing command to 0. An optional argument
specifies how many commands to advance by. Note that only the time
they are run is set to zero, so channels will be very briefly
activated. See the clear command to avoid this.
* `clear`: No argument, removes any future commands. Current command
remains executing. To clear all commands and stop all operations,
first do a `clear`, followed by an `adv`.
* `ping`: No argument, used to verify communication works.
* `setunset`: First argument is channel, second argument is 0 or 1,
specifying to turn the channel off (0) or on (1).

The following commands are queued. The first argument is the number
of milliseconds to run the command for before advancing to the next
command. The available commands are:
* `runfor`: And additional argument specifies the channel. The
channel will be set to on, and then when the command completes, The
channel will be set to off.
* `waitfor`: Wait for the specified time before advancing to the next
enqueued command.

Notes
-----
[LoRa Irrigation](LORA-IRR.md): Control over LoRa radio.

Significant portions of this code is copied from various reference
implementations. Basic review shows that it is of questionable quality.
It would be good to fully review all the code under stm32 and loramac
for any bugs and other issues. The buffer handling has already been
improved, but there is still plety of work that can be done to improve
it further.
[RS-485 USB HID KVM](RS485HID.md): USB Keyboard over RS-485

<!-- Markdeep: --><style class="fallback">body{visibility:hidden;white-space:pre;font-family:monospace}</style><script src="markdeep.min.js" charset="utf-8"></script><script src="https://casual-effects.com/markdeep/latest/markdeep.min.js" charset="utf-8"></script><script>window.alreadyProcessedMarkdeep||(document.body.style.visibility="visible")</script>

+ 54
- 0
RS485HID.md View File

@@ -0,0 +1,54 @@
USB Keyboard over RS-485
========================

The project is to have a device that appears to a computer as a USB
keyboard, but it receives it's key presses from another computer via an
RS-485 multidrop bus. It will use the Syote library for communications,
and as there can be multiple devices on the bus, a single gateway
controller can service multiple such keyboards, making it a nice option
for an IPMI like solution. It is also expected that GPIOs of the device
can be used to control the power switches, along with observing various
LEDs. As most microcontrollers have an ADC, it could theoretically
support beeps from the PC speaker.

Devices
-------

These are the device that are under conideration for the project:

### STM32F103C8

64KB flash

Currently looking at USART3, B0 for DE, B1 for RE.

Table 5 pin definitions of stm32f103c8.pdf
(all can be remapped)

USART has idle line detction

USART1_TX PA9 5v tollerant
USART1_RX PA10 5v tollerant
USART2_TX PA2 NOT 5v tollerant
USART2_RX PA3 NOT 5v tollerant
USART3_TX PB10 5v tollerant
USART3_RX PB11 5v tollerant

flash: 0/x
boot loader (an2606) pattern 1: 1/0
embedded sram: 1/1
top jumper BOOT0/44
bottom jumper BOOT1/PB2/20

### MAX485

This requires a 5V VCC. There is a 3.3V part called the MAX3485, but
as often micros can be 5V tollerant, and the MAX485 is fine w/ logic
levels down to 2V, this should be fine.

D - Driver
DE Driver enable (high enable)
DI Driver input
R - Receiver
RO Receiver output
/RE Receiver enable (low enable)

Loading…
Cancel
Save