1
0
Fork 0
Browse Source

Merge branch 'unstable' of return-to-ritherdon/light-meter into stable

Craig Oates 3 years ago committed by Gitea
parent
commit
0b9b295036
  1. 144
      README.md
  2. BIN
      attachments/hardware-layout.png
  3. BIN
      attachments/light-meter-extension.png
  4. BIN
      attachments/light-meter-logo.png
  5. BIN
      attachments/main-proj-architecture.png
  6. BIN
      attachments/midpoint-logo.png
  7. BIN
      attachments/relay-logo.png
  8. BIN
      attachments/return-to-ritherdon-project-overview.png
  9. BIN
      attachments/rtrp-logo.png
  10. 126
      cli_meter.py
  11. 129
      light_meter.py
  12. 9
      make-log-files.sh
  13. 42
      shutdown.sh
  14. 51
      startup.sh

144
README.md

@ -1,3 +1,143 @@
# light-meter
# ![return-to-ritherdon-project-logo](attachments/rtrp-logo.png) Return to Ritherdon Project
A Python based project which measures the light levels of an environment and forwards those measurements on to a server (for documentation purposes). This project is one of several which forms the software-side of the "Return to Ritherdon" project.
**Disclosure: This file was written by Craig Oates and all information I have cited about the other parties where taken from their respective websites. I have, also, applied minor editing in places to help readability - in the context of this file. Please use the links listed below for the original/official version of the sourced material. Craig is the author of the software in this repository.**
## Project Overview: Return to Ritherdon
"Return to Ritherdon" is a two year residency at Ritherdon & Co Ltd, a manufacturer of metal enclosures based in Darwen, Lancashire U.K. It was devised by artist Nicola Ellis and funded by Arts Council England.
- [Nicola Ellis](http://www.nicolaellis.com)
- [Ritherdon](https://www.ritherdon.co.uk/about-us/)
- [Arts Council England](https://www.artscouncil.org.uk/)
### About the Artist: Nicola Ellis
Nicola is interested in the properties, value, function and circulation of materials. She has a current focus on metals and the companies that work with them, her work draws on the visual and spoken language of industry operations, fabrication and profiling processes. The parameters for her sculpture, installation, drawings and videos include relationships between people, businesses and technology.
### About the Manufacturer: Ritherdon
Established in 1895, Ritherdon have been manufacturing a variety of electrical enclosures and related products for many decades. A combination of a passion for innovation with close working relationships with their customers means that they are continually developing and expanding their product ranges.
### About: Arts Council England
They were set up in 1946, by Royal Charter, to champion and develop art and culture across the country. They are governed by an Executive Board and National and Area Councils.
- [Arts Council England's Royal Charter](https://www.artscouncil.org.uk/sites/default/files/download-file/Consolidated_Royal_Charter_2013.pdf)
## Project Overview: "Unnamed at time of writing"
![Main Project Overview](attachments/return-to-ritherdon-project-overview.png)
This repo. (Light-Meter) is part of a bigger project which is one of several artworks created by Nicola Ellis throughout the course of the residency. For the sake of brevity, this file will focus on the technical aspects of the project. The same is true for all files in the repository.
The overall project ("UNNAMED" not Return to Ritherdon) consists of three separate/smaller projects. The names of the smaller projects are a by-product of the software development process. You should not view them as individual pieces within the overall project. From an artwork point-of-view, "UNNAMED ART WORK" is one piece. The name of the software projects are "light-meter" (which is this one), "mid-point" and "relay". All three projects reside in their own git repositories. You can find the other project's repositories at the following links:
- [Mid-Point](https://git.abbether.net/return-to-ritherdon/midpoint)
- [Relay](https://git.abbether.net/return-to-ritherdon/relay)
The (technical) aim of the project is to turn a set of lights on at the gallery when the welding machines are active in the welding booths at Ritherdon. The solution we arrived at was a three-stage process. The stages are as follows:
1. Monitor the light levels in the welding booths at Ritherdon and send that information to a sever.
2. Receive the light readings and store them in a database and make them available for others to access.
3. Have the lights installed at the gallery connected to wi-fi enabled relays which request the latest light readings from the server. If the readings are above a certain threshold, have the light in the gallery turn on (otherwise, turn off). The relays are responsible for turning the lights on and off.
Each step should require no human intervention.
For more information on how each project accomplishes its task, please use the (repo.) links above. Otherwise, here is an diagram to help explain the three stages mentioned above.
![main-proj-architecture](attachments/main-proj-architecture.png)
## ![light-meter-logo](attachments/light-meter-logo.png) Light-Meter
## Project Overview: Light-Meter
This is a Python 3 based project. I wrote the code in this repository to run on a Raspberry Pi in an unmanned capacity. The main machine I tested the code on was a Raspberry Pi 4 (2 G.B.) but I did get it to work on a Raspberry Pi 1 (Model B) -- so do with that what you will. Software-wise, I have only used Raspbian Buster throughout the development of this project. So, I do not know how well this will run on other Linux-based operating systems. I must stress, this is a Linux-only endeavour. This will not work on Mac OS and Windows -- that includes Windows Subsystem for Linux (W.S.L. 1 and 2).
For the final version (exhibition context), the intended operating system is Raspbian Buster Lite. That is the version of Raspbian without a (desktop) G.U.I.
The full list of parts required for this project is as follows:
- [Raspbian](https://www.raspberrypi.org/downloads/raspbian/) (You can use the G.U.I. or "headless" version)
- [Raspberry Pi 4](https://www.raspberrypi.org/products/raspberry-pi-4-model-b/) (I am assuming you have the appropriate power cable, S.D. cards Etc.)
- [Light Sensor] (https://www.amazon.co.uk/s/ref=as_li_ss_tl?url=search-alias=aps&field-keywords=LDR&linkCode=ll2&tag=pimylifeup-21&linkId=8662811b5623ce86540420c7e8ce0268&language=en_GB) (I tend to use "light sensor" and "light meter" interchangeably -- sorry if confusing)
- [2 x 1kΩ Resistor](https://www.amazon.co.uk/1K-Resistors-50-Pack-Electronics/dp/B00JGUE0L0)
- [330nF Capacitor](https://www.alibaba.com/product-detail/ODOELEC-334-0-33uf-330nf-50v_60626664828.html)
- [Breadboard](https://thepihut.com/products/raspberry-pi-breadboard-half-size) (Optional but recommended if you do not know how-to solder or you don't feel comfortable doing it)
- [Jumper Wires] (https://thepihut.com/products/rpi-premium-jumper-wires-40pk-male-female-100mm?_pos=18&_sid=4d08c5200&_ss=r) (I'm assuming you are using the breadboard. If are not, you might need different cables like Female-to-Female or ones without a connector on the ends)
### Initial Raspbian Set-up
Upon the initial installation of Raspbian on to the Pi, you need to make sure the following is established:
- The username is "rtrp".
- The host-name is "factory#" (where "#" is a number between 1 and 3).
- The Pi is set to auto-login with the "rtrp" account.
You can set the Pi up to automatically login to the desktop but the recommended option is to login to a "headless" environment (I.E. console-mode). Remember, you can only log into the desktop environment if your version of Raspbian has one. The final version of this project does not expect one.
### Hardware Preparations
#### Network Connections
Before installing the Raspberry Pi in its final location (gallery/exhibition), make sure it can connect to the galleries internet -- either via wi-fi or Ethernet cable. If you use an Ethernet cable, you do not need to do anything, but the wi-fi requires a little work on the command-line (if you are using a "headless" version of Raspbian). If you are unsure how to connect to a router via wi-fi, use the following link to learn how:
- [Raspbian Wi-Fi Tutorial](https://www.raspberrypi.org/documentation/configuration/wireless/wireless-cli.md)
The easiest way to do this is via the "raspi-config" file/command. You can access it by entering `sudo raspi-config` into the console and entering the "Network Options" section. For the other ways of connecting to a wireless network, I recommend you read the tutorial because it can get complicated and the options are numerous.
#### Light-Meter Set-Up
The light-meter is a custom addition to the Raspberry Pi which is built using the Pi's general-purpose input and output (G.P.I.O.) pins. You can see the layout of the components connected to the Pi in the diagram below.
![hardware-layout](attachments/hardware-layout.png)
Depending on the type of enclosure you use, you might find it more convenient to have the actual light-meter (component) connected to the breadboard with a pair of jumper wires. To help explain the point, please see the image below.
![light-meter-extension](attachments/light-meter-extension.png)
#### Power Supply
I am under the impression this device will run in a fully functional building (I.E. the Ritherdon factory). Because of this, I have taken no precautions or steps to assume a loss of power beyond the typical scenarios found in a U.K. factory environment. Because of this, I have no idea how the Pi will operate whilst powered via batteries or other "mobile" power sources. With that said, make sure you can place the Pi where you want it whilst still being able to power it.
### Software Preparations
Depending on what version of Linux/Raspbian you are running, you might need to install some dependencies. I have listed the common ones I came across whilst developing this project. But, you might need to rely on your own cunning to track down missing dependencies.
```bash
# Don't forget to apt update and upgrade first...
sudo apt install python3-pip
sudo pip3 install requests
sudo pip3 install RPi.GPIO
# You might need to install RPi.GPIO via apt
sudo apt install python3-rpi.gpio
# I will explain why this is here below...
mkdir ~/repos
```
**Note: I decided not to create/use a (Python) virtual environment because of the projects objectives. I expect the software in this repository to run on an unmanned machine with only one task to complete. The environment this project will run in/on will not change throughout the course of the exhibition. So, the redundancies afforded by the virtual environment are not needed.**
When you clone this repository, you need to make sure you clone it into the following location: `/home/rtrp/repos/light-meter/`. From there, run the following command,
```bash
# This must be the first thing you run after you have cloned
# the repository.
. ~/repos/light-meter/make-log-files.sh
```
You can test the code is working properly by running it. You can do that by entering `sudo python3 ~/repos/light-meter/cli_meter.py` into the console. This is assuming the server specified in "cli_meter.py" is set-up and working as intended.
**Note: For some reason, I had trouble running "cli_meter.py" without `sudo`. I would sometimes get an error message saying "RPi.GPIO is not available/installed" (paraphrased). If you manage to get it working without the use of `sudo`, remain as you were. Otherwise, keep a mental note of this if you come across the problem.**
When you are ready to run this project as intended, you can set-up a cron-job for it. To do so enter `sudo crontab -e` into the console (see note about `sudo` above). You might need to select an editor if this is your first time setting up a cron-job. I tend to go for Nano -- which is option "1" most of the time. When the crontab file opens, enter the following commands at the bottom of the file,
```bash
@reboot bash /home/rtrp/repos/light-meter/startup.sh &
00 16 * * * /home/rtrp/repos/light-meter/shutdown.sh
```
These tasks make the Raspberry Pi send a message to the sever to indicate its/their status ("on" or "off") and makes the "cli_meter.py" script run. At this point, you should be able to walk away and let the Pi do its thing. This is assuming the server is up and running and the Pi is connected to the world-wide-web. If all is successful, you will notice the Pi will turn itself off at 14:00 (4 p.m. -- when the factory begins shutting down for the day) and will start sending light reading when you turn it on without any input from you. Unfortunately, the Pi can only manage the shutdown procedure on its own. You will need to turn it on. (This has been accounted whilst the exhibition is open.)
To make sure the Pi sends a "powering down" message to the server, I tend you create an alias called `powerdown`. When you type this into the console, it runs the "shutdown.sh" script -- which has the shutdown command within it. To make the alias permanent, enter `alias='~/repos/light-meter/shutdown.sh'` into `~/.bashrc`. This is easier to test the bespoke shutdown procedure is working as intended. You can, also, adjust the time in the crontab or run the script by running the script like you normally would but I find them to be frustrating to do in this instance.

BIN
attachments/hardware-layout.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

BIN
attachments/light-meter-extension.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 520 KiB

BIN
attachments/light-meter-logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
attachments/main-proj-architecture.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

BIN
attachments/midpoint-logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
attachments/relay-logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

BIN
attachments/return-to-ritherdon-project-overview.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

BIN
attachments/rtrp-logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

126
cli_meter.py

@ -0,0 +1,126 @@
#!/usr/bin/python3
"""
C.L.I. Meter
======================================================================
I wrote this script with the intention of using it as one part within
a single cron-job. The cron-job begins with "startup.sh" and that,
in-turn, will call this script when it is ready to do so.
This scrip takes a reading of the current light-level in the room,
using the Raspberry Pi (and the attached light-meter attached) it is
running on. From there, it sends a J.S.O.N. object to the server
(specified at "api_url"). The process is then repeated until it is
killed manually or via "shutdown.sh" (as a cron-job most likely).
You can use this script on Raspberry Pis without a G.U.I. because it
is all C.L.I. based. If you prefer something more graphical, you can
use "light_meter.py" -- which is part of this repository at time of
writing.
To end this script manually, use the usual "Ctrl-c" keyboard
interrupt -- or something like Htop... doesn't matter.
"""
import json
import RPi.GPIO as GPIO
import time
import math
import requests
from datetime import datetime
import platform
# import pdb # For testing.
# Using BCM (Broadcom) names when referencing the GPIO pins.
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(True)
a_pin = 18 # Charges the capacitor.
b_pin = 23 # Discharges the capacitor.
device_id = platform.node() # For servers logs.
def get_api_url():
# Yes, I know I could do this better. Stop moaning.
if (device_id == "factory1"):
return "http://ritherdon.abbether.net/api/readings/add/1"
elif (device_id == "factory2"):
return "http://ritherdon.abbether.net/api/readings/add/2"
api_url = get_api_url()
def discharge():
GPIO.setup(a_pin, GPIO.IN)
GPIO.setup(b_pin, GPIO.OUT)
GPIO.output(b_pin, False)
time.sleep(0.01) # 0.01 -- initial/default value.
def charge_time():
GPIO.setup(b_pin, GPIO.IN)
GPIO.setup(a_pin, GPIO.OUT)
GPIO.output(a_pin, True)
t1 = time.time()
while not GPIO.input(b_pin):
pass
t2 = time.time()
return (t2 - t1) * 1000000
def analog_read():
discharge()
return charge_time()
def read_resistance():
n = 20
total = 0
for i in range(1, n):
total = total + analog_read()
reading = total / float(n)
resistance = reading * 6.05 - 939
return resistance
def light_from_r(R):
return math.log(1000000.0/R) * 10.0
def get_timestamp():
return datetime.now().strftime(("%Y-%m-%d %H:%M:%S"))
def push_reading(lvalue):
time = get_timestamp()
headers = {"content-type": "application/json"}
payload = {"reading": int(lvalue), "time": time,
"token": "QWERTYuiopasdfghjklzxcvbnm_1234567890"}
# print(payload) # For testing.
res = requests.post(api_url, data=json.dumps(payload), headers=headers)
def update_reading():
light = light_from_r(read_resistance())
reading_str = "{:.0f}".format(light)
# print(reading_str) # For testing.
push_reading(light)
def main():
try:
while True:
# pdb.set_trace() # For testing.
update_reading()
except KeyboardInterrupt:
print("Keyboard Interrupt: quitting program.")
except:
print("Error updating light reading...")
finally:
print("Cleaning up GPIO before closing...")
GPIO.cleanup()
if __name__ == "__main__":
# time.sleep(60) # For testing/debugging.
main()

129
light_meter.py

@ -0,0 +1,129 @@
# 07_light_meter.py
# From the code for the Electronics Starter Kit for the Raspberry Pi by MonkMakes.com
#from Tkinter import *
from tkinter import *
import json
import RPi.GPIO as GPIO
import time, math
import requests # for pushing the readings to roaming-light-api
from datetime import datetime
# Configure the Pi to use the BCM (Broadcom) pin names, rather than the pin positions
GPIO.setmode(GPIO.BCM)
# This project uses a photoresistor, a component whose resistance varies with the light falling on it.
# To measure its resistance, the code records the time it takes for a capacitor to fill
# when supplied by a current passing through the resistor. The lower the resistance the faster
# it fills up.
#
# You can think of a capacitor as a tank of electricity, and as it fills with charge, the voltage
# across it increases. We cannot measure that voltage directly, because the Raspberry Pi
# does not have an analog to digital convertor (ADC or analog input). However, we can time how long it
# takes for the capacitor to fill with charge to the extent that it gets above the 1.65V or so
# that counts as being a high digital input.
#
# For more information on this technique take a look at:
# learn.adafruit.com/basic-resistor-sensor-reading-on-raspberry-pi
# The code here is based on that in the Raspberry Pi Cookbook (Recipes 12.1 to 12.3)
# Pin a charges the capacitor through a fixed 1k resistor and the thermistor in series
# pin b discharges the capacitor through a fixed 1k resistor
a_pin = 18
b_pin = 23
# empty the capacitor ready to start filling it up
def discharge():
GPIO.setup(a_pin, GPIO.IN)
GPIO.setup(b_pin, GPIO.OUT)
GPIO.output(b_pin, False)
time.sleep(0.01) # 0.01 -- initial value
# return the time taken for the voltage on the capacitor to count as a digital input HIGH
# than means around 1.65V
def charge_time():
GPIO.setup(b_pin, GPIO.IN)
GPIO.setup(a_pin, GPIO.OUT)
GPIO.output(a_pin, True)
t1 = time.time()
while not GPIO.input(b_pin):
pass
t2 = time.time()
return (t2 - t1) * 1000000
# Take an analog readin as the time taken to charge after first discharging the capacitor
def analog_read():
discharge()
return charge_time()
# Convert the time taken to charge the cpacitor into a value of resistance
# To reduce errors, do it 100 times and take the average.
def read_resistance():
n = 20
total = 0;
for i in range(1, n):
total = total + analog_read()
reading = total / float(n)
resistance = reading * 6.05 - 939
return resistance
def light_from_r(R):
# Log the reading to compress the range
return math.log(1000000.0/R) * 10.0
# Gets the time and date (at the time of the reading)
def get_timestamp():
return datetime.now().strftime(("%Y-%m-%d %H:%M:%S"))
# Pushes the reading to roaming-light-api
def push_reading(lvalue):
act = False
if lvalue > 20:
act = True
time = get_timestamp()
headers = {"content-type": "application/json"}
# payload = { "active": act, "light-reading": int(lvalue), "timestamp": time }
payload = { "reading": int(lvalue), "time": time}
# res = requests.post("http://192.168.1.228:5000/api/readings/add/1", data=json.dumps(payload), headers=headers)
res = requests.post("http://ritherdon.abbether.net/api/readings/add/1", data=json.dumps(payload), headers=headers)
# print(int(lvalue))
# print(res.status_code)
print(payload)
# group together all of the GUI code into a class called App
class App:
# this function gets called when the app is created
def __init__(self, master):
self.master = master
frame = Frame(master, bg="black")
frame.pack(expand=1)
label = Label(frame, text='Light', fg="white", bg="black", font=("Helvetica", 32))
label.grid(row=0)
self.reading_label = Label(frame, text='12.34', bg="black", fg="white", font=("Helvetica", 110))
self.reading_label.grid(row=1)
self.update_reading()
# Update the reading
def update_reading(self):
light = light_from_r(read_resistance())
reading_str = "{:.0f}".format(light)
self.reading_label.configure(text=reading_str)
self.master.after(200, self.update_reading)
push_reading(light)
# Set the GUI running, give the window a title, size and position
root = Tk()
# Uncomment when app. is ready for deployment
#root.attributes("-fullscreen", True)
root.attributes("-zoomed", True)
root.configure(background="black")
root.wm_title('Light Meter')
app = App(root)
root.geometry("400x300+0+0")
try:
root.mainloop()
finally:
print("Cleaning up")
GPIO.cleanup()

9
make-log-files.sh

@ -0,0 +1,9 @@
#!/bin/bash
# This script creates the log files and the fold they will reside in.
# This script must be executed before you set-up the start-up and
# shutdown cron-jobs on the system.
mkdir ~/logs/
touch ~/logs/startup-logs.txt
touch ~/logs/shutdown-logs.txt

42
shutdown.sh

@ -0,0 +1,42 @@
#!/bin/bash
# Shutdown Script
# ====================================================================
# I wrote this script with the intention of using it as part of a
# cron-job. Before you set up the cron-job, you must make sure you
# have either ran the "make-log-files.sh" script or created the
# appropriate log file and folder at the location specified at
# "logFile" below.
# As an aside, I usually set-up as alias for "shutdown" called,
# "powerdown". When I enter "powerdown" into the terminal, this script
# should ran and then the "shutdown" command is ran at the end. The
# "sleep" part is to give "curl" a bit of a buffer to finish its task.
# I have found it sometimes fails otherwise.
logDate=$(date '+%Y-%m-%dT%TZ')
logFile="/home/rtrp/logs/shutdown-logs.txt"
mainURL="http://ritherdon.abbether.net/api/status/update" # Make sure this is valid.
getApiUrl () {
case $HOSTNAME in
(factory1) apiURL="${mainURL}/1";;
(factory2) apiURL="${mainURL}/2";;
(factory3) apiURL="${mainURL}/3";;
(gallery1) apiURL="${mainURL}/4";;
(gallery2) apiURL="${mainURL}/5";;
(gallery3) apiURL="${mainURL}/6";;
esac
}
logStatusChange () {
cat << EOF >> $logFile
$logDate
EOF
}
logStatusChange
getApiUrl
curl -X POST --header 'Content-Type: application/json' --header 'Accept: text/html' -d '{"status": "off", "time": "'${logDate}'", "token": "QWERTYuiopasdfghjklzxcvbnm_1234567890"}' "${apiURL}"
/sbin/shutdown -h now

51
startup.sh

@ -0,0 +1,51 @@
#!/bin/bash
# Startup Script
# ====================================================================
# I wrote this script with the intention of using it to start a
# cron-job -- when the Raspberry Pi is turned on. Before you set the
# cron-job up, you must make sure you have either ran the
# "make-log-files.sh" script or created the appropriate log file and
# folder at the location specified at "logFile" below.
# ====
# Please note: This script calls another (Python) script at the end of
# it. The code in the Python script (cli_meter.py) is an infinite-loop
# so you will need to kill it manually or turn-off the Raspberry Pi.
# ====
# I put the "sleep 60" call at the start to reduce any errors
# occurring because a part of the system (I.E. curl) has not finished
# loading. 60 seconds is a little excessive but I wrote this script
# with the expectation of it running on an unmanned Raspberry Pi. So,
# reliable/consistent behaviour is my preference over "fast start-up
# times".
sleep 60
logDate=$(date '+%Y-%m-%dT%TZ')
logFile="/home/rtrp/logs/startup-logs.txt"
mainURL="http://ritherdon.abbether.net/api/status/update" # Make sure this is valid.
getApiUrl () {
case $HOSTNAME in
(factory1) apiURL="${mainURL}/1";;
(factory2) apiURL="${mainURL}/2";;
(factory3) apiURL="${mainURL}/3";;
(gallery1) apiURL="${mainURL}/4";;
(gallery2) apiURL="${mainURL}/5";;
(gallery3) apiURL="${mainURL}/6";;
esac
}
logStatusChange () {
cat << EOF >> $logFile
$logDate
EOF
}
logStatusChange
getApiUrl
curl -S -X POST --header 'Content-Type: application/json' --header 'Accept: text/html' -d '{"status": "on", "time": "'${logDate}'", "token": "QWERTYuiopasdfghjklzxcvbnm_1234567890"}' "${apiURL}"
python3 /home/rtrp/repos/light-meter/cli_meter.py