├── ESPNow_water_tank ├── ESPNow_local │ ├── main.py │ ├── boot.py │ └── local.py ├── ESPNow_remote │ ├── main.py │ ├── boot.py │ └── remote.py └── ESPNow_repeater │ ├── main.py │ ├── boot.py │ └── repeater.py └── README.md /ESPNow_water_tank/ESPNow_local/main.py: -------------------------------------------------------------------------------- 1 | import local 2 | -------------------------------------------------------------------------------- /ESPNow_water_tank/ESPNow_remote/main.py: -------------------------------------------------------------------------------- 1 | import remote 2 | -------------------------------------------------------------------------------- /ESPNow_water_tank/ESPNow_repeater/main.py: -------------------------------------------------------------------------------- 1 | import repeater 2 | -------------------------------------------------------------------------------- /ESPNow_water_tank/ESPNow_local/boot.py: -------------------------------------------------------------------------------- 1 | # This file is executed on every boot (including wake-boot from deepsleep) 2 | #import esp 3 | #esp.osdebug(None) 4 | #import uos, machine 5 | #uos.dupterm(None, 1) # disable REPL on UART(0) 6 | #import gc 7 | #import webrepl 8 | #webrepl.start() 9 | #gc.collect() 10 | -------------------------------------------------------------------------------- /ESPNow_water_tank/ESPNow_remote/boot.py: -------------------------------------------------------------------------------- 1 | # This file is executed on every boot (including wake-boot from deepsleep) 2 | #import esp 3 | #esp.osdebug(None) 4 | #import uos, machine 5 | #uos.dupterm(None, 1) # disable REPL on UART(0) 6 | #import gc 7 | #import webrepl 8 | #webrepl.start() 9 | #gc.collect() 10 | -------------------------------------------------------------------------------- /ESPNow_water_tank/ESPNow_repeater/boot.py: -------------------------------------------------------------------------------- 1 | # This file is executed on every boot (including wake-boot from deepsleep) 2 | #import esp 3 | #esp.osdebug(None) 4 | #import uos, machine 5 | #uos.dupterm(None, 1) # disable REPL on UART(0) 6 | #import gc 7 | #import webrepl 8 | #webrepl.start() 9 | #gc.collect() 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP-Now-repeater 2 | ESP-Now repeater or link 3 | 4 | ESP-Now water tank level monitoring system 5 | 6 | 7 | This project is an example of using ESP-Now in a situation where a repeater or link is required. 8 | 9 | As the path to my house supply water tank is obstructed by trees, as well as being about 300 mtrs from the house, I needed a system that uses a repeater or link to ensure that a solid signal gets back to the house. 10 | 11 | Three ESP32-WROOM32U (Generic) are being used with the latest Micropython port of ESP-Now found at: 12 | 13 | https://github.com/glenn20/micropython-espnow-images 14 | 15 | The system consists of a remote (with a float level switch), a repeater and a local (with an LED to indicate remote state or a siren). 16 | 17 | The remote is solar powered, so it uses lightsleep() to conserve battery power. Once a minute it wakes up and sends the sensor state to the repeater. There is an option of running machine.WDT() to help keep this unit running. This unit is connected to a small patch antenna pointed at the repeater. 18 | 19 | The repeater relays the sensor state to the local unit. This unit is connected to a "WiFi" whip (uni-directional) antenna. 20 | 21 | The local unit is wired to another "WiFi" whip antenna. 22 | 23 | If using a watchdog timer ensure that the timeout period is longer than CYCLE_TIME, 70 seconds for a CYCLE_TIME of 60 seconds. 24 | 25 | Notes: 26 | 27 | - for initial setup the lines #mac = w0.config('mac') and 28 | #print(mac) need to be uncommented so that the MAC address 29 | of the device can be determined 30 | - the wdt_feed(), if used, needs to go in the for loop on the 31 | repeater and the local 32 | - subsequent testing indicates that using the same CYCLE_TIME 33 | for all three units is probably not the most reliable approach. 34 | Testing continues with 60 seconds for the remote, 65 seconds 35 | for the repeater and 70 seconds for the local. 36 | 37 | Best place to get help would be on the Micropython forum: 38 | https://forum.micropython.org/viewtopic.php?f=18&t=9177 39 | 40 | 41 | Credits to: 42 | 43 | glenn20 for the "iterating over the ESPNow singleton" example at 44 | https://forum.micropython.org/viewtopic.php?f=18&t=9177&start=110 45 | -------------------------------------------------------------------------------- /ESPNow_water_tank/ESPNow_repeater/repeater.py: -------------------------------------------------------------------------------- 1 | # repeater.py for ESPNow repeater link. 2 | 3 | # The MIT License (MIT) 4 | # 5 | # Copyright (c) 2021 David Festing 6 | # Copyright (c) 2021 Glenn Moloney https://github.com/glenn20/micropython-espnow-images 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy 9 | # of this software and associated documentation files (the "Software"), to deal 10 | # in the Software without restriction, including without limitation the rights 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | # copies of the Software, and to permit persons to whom the Software is 13 | # furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in 16 | # all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | # THE SOFTWARE. 25 | 26 | 27 | # V1 18 Sept 2021 28 | 29 | 30 | import network 31 | from esp import espnow 32 | import utime 33 | import machine 34 | import watchdog_timer as wdt 35 | #from machine import WDT 36 | 37 | 38 | CYCLE_TIME = 65 # seconds 39 | REBOOT_DELAY = 5 # seconds 40 | local = b'\x08:\xf2\xacj\xe4' 41 | remote = b'\x8c\xaa\xb5\x8b\x90X' 42 | #wdt = WDT(timeout = (CYCLE_TIME + 10) * 1000) # enable it with a timeout 43 | #wdt.feed() 44 | 45 | 46 | 47 | def reboot(delay = REBOOT_DELAY): 48 | # print a message and give time for user to pre-empt reboot 49 | # in case we are in a (battery consuming) boot loop 50 | print (f'Rebooting device in {delay} seconds (Ctrl-C to escape).') 51 | # or just machine.deepsleep(delay) or lightsleep() 52 | utime.sleep(delay) 53 | machine.reset() 54 | 55 | 56 | try: 57 | print ('you have 5 seconds to do Ctrl-C if you want to edit the program') 58 | utime.sleep(5) 59 | 60 | w0 = network.WLAN(network.STA_IF) 61 | # print (w0.config('mac')) 62 | e0 = espnow.ESPNow() 63 | # print (e0) 64 | 65 | # these functions generate exceptions on error - always return None 66 | e0.init() 67 | # so that we timeout before the wdt needs resetting 68 | e0.config(timeout = CYCLE_TIME * 1000) 69 | e0.add_peer(local) 70 | except KeyboardInterrupt as err: 71 | raise err # use Ctrl-C to exit to micropython repl 72 | except Exception as err: 73 | print ('Error initialising espnow:', err) 74 | reboot() 75 | 76 | 77 | try: 78 | print ('waiting for initial msg from the remote') 79 | 80 | w0.active(True) 81 | 82 | for mac, msg in e0: 83 | # wdt.feed() 84 | if mac == remote: 85 | wanted_msg = msg.decode('utf-8') 86 | print (wanted_msg) 87 | e0.send(local, msg, True) 88 | elif mac is None: 89 | pass # timedout waiting for a message 90 | else: 91 | print ('Recv from {}: "{}"'.format(mac, msg)) 92 | except KeyboardInterrupt as err: 93 | raise err # use Ctrl-C to exit to micropython repl 94 | except Exception as err: 95 | # all other exceptions cause a reboot 96 | print ('Error during execution:', err) 97 | reboot() 98 | -------------------------------------------------------------------------------- /ESPNow_water_tank/ESPNow_local/local.py: -------------------------------------------------------------------------------- 1 | # local.py for ESPNow repeater link. 2 | 3 | # The MIT License (MIT) 4 | # 5 | # Copyright (c) 2021 David Festing 6 | # Copyright (c) 2021 Glenn Moloney, https://github.com/glenn20/micropython-espnow-images 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy 9 | # of this software and associated documentation files (the "Software"), to deal 10 | # in the Software without restriction, including without limitation the rights 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | # copies of the Software, and to permit persons to whom the Software is 13 | # furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in 16 | # all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | # THE SOFTWARE. 25 | 26 | 27 | # V1 18 Sept 2021 28 | 29 | 30 | import network 31 | from esp import espnow 32 | import utime 33 | import machine 34 | from machine import Pin 35 | import watchdog_timer as wdt 36 | #from machine import WDT 37 | 38 | 39 | CYCLE_TIME = 70 # seconds 40 | REBOOT_DELAY = 5 # seconds 41 | LED_PIN = 27 42 | repeater_mac = b'\x08:\xf2\xab^\x04' 43 | #wdt = WDT(timeout = (CYCLE_TIME + 10) * 1000) # enable it with a timeout 44 | #wdt.feed() 45 | 46 | 47 | def reboot(delay = REBOOT_DELAY): 48 | # print a message and give time for user to pre-empt reboot 49 | # in case we are in a (battery consuming) boot loop 50 | print (f'Rebooting device in {delay} seconds (Ctrl-C to escape).') 51 | # or just machine.deepsleep(delay) or lightsleep() 52 | utime.sleep(delay) 53 | machine.reset() 54 | 55 | 56 | try: 57 | print ('you have 5 seconds to do Ctrl-C if you want to edit the program') 58 | utime.sleep(5) 59 | 60 | led_pin = Pin(LED_PIN, Pin.OUT) # LED drive pin 61 | w0 = network.WLAN(network.STA_IF) 62 | # print (w0.config('mac')) 63 | e0 = espnow.ESPNow() 64 | # print (e0) 65 | 66 | # these functions generate exceptions on error - always return None 67 | e0.init() 68 | # so that we wake up and reset the wdt before it times out 69 | e0.config(timeout = CYCLE_TIME * 1000) 70 | e0.add_peer(repeater_mac) 71 | except KeyboardInterrupt as err: 72 | raise err # use Ctrl-C to exit to micropython repl 73 | except Exception as err: 74 | print ('Error initialising espnow:', err) 75 | reboot() 76 | 77 | 78 | try: 79 | print ('waiting for initial msg from the repeater') 80 | 81 | w0.active(True) 82 | 83 | for mac, msg in e0: 84 | # wdt.feed() 85 | if mac == repeater_mac: 86 | msg = msg.decode('utf-8') 87 | print (msg) 88 | 89 | # alarm off if 'level OK', anything else is an alarm 90 | led_pin.value(0 if msg == 'level OK' else 1) 91 | elif mac == None: 92 | pass # timed out waiting for message 93 | else: 94 | print ('Recv from {}: "{}"'.format(mac, msg)) 95 | except KeyboardInterrupt as err: 96 | raise err # use Ctrl-C to exit to micropython repl 97 | except Exception as err: 98 | # all other exceptions cause a reboot 99 | print ('Error during execution:', err) 100 | reboot() 101 | -------------------------------------------------------------------------------- /ESPNow_water_tank/ESPNow_remote/remote.py: -------------------------------------------------------------------------------- 1 | # remote.py for ESPNow repeater link. 2 | 3 | # The MIT License (MIT) 4 | # 5 | # Copyright (c) 2021 David Festing 6 | # Copyright (c) 2021 Glenn Moloney https://github.com/glenn20/micropython-espnow-images 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy 9 | # of this software and associated documentation files (the "Software"), to deal 10 | # in the Software without restriction, including without limitation the rights 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | # copies of the Software, and to permit persons to whom the Software is 13 | # furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in 16 | # all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | # THE SOFTWARE. 25 | 26 | 27 | # V1 18 Sept 2021 28 | 29 | 30 | import network 31 | from esp import espnow 32 | import utime 33 | import machine 34 | from machine import Pin 35 | #from machine import WDT 36 | 37 | 38 | CYCLE_TIME = 60 # seconds 39 | REBOOT_DELAY = 5 # seconds 40 | WATER_LEVEL_PIN = 27 41 | repeater_mac = b'\x08:\xf2\xab^\x04' 42 | #wdt = WDT(timeout = (CYCLE_TIME + 10) * 1000) # enable it with a timeout 43 | #wdt.feed() 44 | 45 | 46 | def reboot(delay = REBOOT_DELAY): 47 | # print a message and give time for user to pre-empt reboot 48 | # in case we are in a (battery consuming) boot loop 49 | print (f'Rebooting device in {delay} seconds (Ctrl-C to escape).') 50 | # or just machine.deepsleep(delay) or lightsleep() 51 | utime.sleep(delay) 52 | machine.reset() 53 | 54 | 55 | try: 56 | print ('you have 5 seconds to do Ctrl-C if you want to edit the program') 57 | utime.sleep(5) 58 | 59 | pin = Pin(WATER_LEVEL_PIN, Pin.IN, Pin.PULL_UP) # water level sensor 60 | 61 | w0 = network.WLAN(network.STA_IF) 62 | # print (w0.config('mac')) 63 | e0 = espnow.ESPNow() 64 | # print (e0) 65 | 66 | # these functions generate exceptions on error - always return None 67 | e0.init() 68 | 69 | # so that we wake up and reset the wdt before it times out 70 | e0.config(timeout = CYCLE_TIME * 1000) 71 | 72 | e0.add_peer(repeater_mac) 73 | except KeyboardInterrupt as err: 74 | raise err # use Ctrl-C to exit to micropython repl 75 | except Exception as err: 76 | print ('Error initialising espnow:', err) 77 | reboot() 78 | 79 | 80 | try: 81 | while True: 82 | # wdt.feed() 83 | 84 | water_status = 'level OK' if pin.value() == 1 else 'level alarm' 85 | print (water_status) 86 | 87 | w0.active(True) # turn on radio only when needed 88 | # if you want to save more battery, set sync=False 89 | # at cost of not knowing if message was received. 90 | retval = e0.send(repeater_mac, water_status, True) 91 | w0.active(False) 92 | 93 | # for testing from remote end 94 | # if (retval != True): 95 | # print ('send did NOT work') 96 | # reboot() 97 | 98 | # wdt.feed() 99 | machine.lightsleep(CYCLE_TIME * 1000) 100 | # wdt.feed() 101 | except KeyboardInterrupt as err: 102 | raise err # use Ctrl-C to exit to micropython repl 103 | except Exception as err: 104 | print ('Error during execution:', err) 105 | reboot() 106 | --------------------------------------------------------------------------------