Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

showff #162

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions src/Components/AreaGraph.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const AreaGraph = ({ name, unit, stats, isGreen, timescale, zoom }) => {
<Paper sx={{ p: 1, width: 170 }}>
<Typography variant="body2">{new Date(point.x * 1000).toLocaleString()}</Typography>
<Typography variant="body1">
{Number.isNaN(point.y) ? "N/A" : `${point.y} ${unit}`}
{Number.isNaN(point.y) ? "N/A" : `${point.y.toFixed(2)} ${unit}`}
</Typography>
</Paper>
);
Expand All @@ -42,15 +42,15 @@ const AreaGraph = ({ name, unit, stats, isGreen, timescale, zoom }) => {
<ResponsiveContainer>
<AreaChart
data={stats}
margin={{ left: -20}} // left margin strays too far; is there a better way to fix?
margin={{ left: -20 }} // left margin strays too far; is there a better way to fix?
>
<defs>
{gradientGood}
{gradientBad}
</defs>
{/* Why is the documentation in ReCharts so bad ;-; Dear maintainer, if you care
enough then feel free to switch libraries lol */}
<CartesianGrid strokeDasharray="3 3"/>
<CartesianGrid strokeDasharray="3 3" />
<XAxis
dataKey="x"
tickFormatter={(unixTime) => new Date(unixTime * 1000).toLocaleString([], { dateStyle: "short", timeStyle: "short" })}
Expand All @@ -60,7 +60,7 @@ const AreaGraph = ({ name, unit, stats, isGreen, timescale, zoom }) => {
style={theme.typography.body2}
interval="preserveStartEnd"
/>
<YAxis style={theme.typography.body2} domain={[0, 'auto']} allowDataOverflow={true}/>
<YAxis style={theme.typography.body2} domain={[0, 'auto']} allowDataOverflow={true} />
<Area
dataKey="y"
name={name}
Expand All @@ -71,7 +71,7 @@ const AreaGraph = ({ name, unit, stats, isGreen, timescale, zoom }) => {
fill={`url(#${gradientName})`}
isAnimationActive
/>
<Tooltip style={theme.typography.body2} content={renderTooltip}/>
<Tooltip style={theme.typography.body2} content={renderTooltip} />
</AreaChart>
</ResponsiveContainer>
);
Expand Down
18 changes: 9 additions & 9 deletions src/Components/GraphCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,24 @@ const GraphCard = ({ name, unit, statKey, loading, stats, tolerance, timescale,
<Stack direction="row" justifyContent="space-between" alignItems="center" sx={{ height: 60, typography: "h3" }}>
<Box sx={overflowStyle}>{name}</Box>
{loading ? "loading"
: !hasData ? "no data"
: Number.isNaN(latestY) ? "it broke :("
: <Stack direction="row" alignItems="flex-end">
<Typography variant="h1">{Math.round(latestY).toString()}</Typography>
<Typography variant="h3">{unit}</Typography>
</Stack>}
: !hasData ? "no data"
: Number.isNaN(latestY) ? "it broke :("
: <Stack direction="row" alignItems="flex-end">
<Typography variant="h1">{latestY.toFixed(1)}</Typography>
<Typography variant="h3">{unit}</Typography>
</Stack>}
</Stack>
);
const content = loading ? (
<Box sx={verticalCenteredStyle}>
<CircularProgress color="info"/>
<CircularProgress color="info" />
<Typography variant="body1">Loading</Typography>
</Box>
) : hasData ? (
<AreaGraph name={name} unit={unit} stats={data} isGreen={inRange} timescale={timescale} zoom={zoom}/>
<AreaGraph name={name} unit={unit} stats={data} isGreen={inRange} timescale={timescale} zoom={zoom} />
) : (
<Box sx={verticalCenteredStyle}>
<WarningAmberIcon color="warning" sx={{ fontSize: 128 }}/>
<WarningAmberIcon color="warning" sx={{ fontSize: 128 }} />
<Typography variant="body1">There doesn't seem to be any data</Typography>
</Box>
);
Expand Down
79 changes: 39 additions & 40 deletions src/Hooks/useTracksStats.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,51 +23,50 @@ Data type of tolerances: {
}
*/

export function useTrackStats(timescale) {
const [loading, setLoading] = useState(true);

// get stats
const [stats, setStats] = useState([]);
useEffect(() => {
setLoading(true);
const lowerTimeBound = Math.floor(Date.now()/1000) - timescale;
const q_states = query( // the data we want to fetch
collection(db, 'stats'),
where('unix_time', '>', lowerTimeBound),
orderBy('unix_time', 'asc')
);
const unsubscribe = onSnapshot(q_states, (snapshot) => { // real-time listener for changes to the queried stats data
setStats(convertStatsSnapshot(snapshot)); //this is the initial data fetch
setLoading(false);
function generateMockData(numPoints, finalStats) {
const now = Math.floor(Date.now() / 1000);
const data = [];
const currentData = finalStats;
for (let i = 0; i < numPoints; i++) {
data.push({
unixTime: now - i * 60 * 5, // go back 5 minutes each time
stats: {
...currentData,
},
});
const timer = setInterval(() => {
const lowerTimeBoundNow = Math.floor(Date.now()/1000) - timescale;
// filter the stats to only include data from the last [timescale] seconds
setStats(prevStats => prevStats.filter((stat) => stat.unixTime > lowerTimeBoundNow));
}, 60 * 1000); // 1 min

// make sure to unsubscribe when the component unmounts so that we don't have a bunch of listeners running
return () => {
unsubscribe();
clearInterval(timer);
// modify currentData by some random amount for each stat
for (const statKey in currentData) {
currentData[statKey] += currentData[statKey] * (Math.random() * 0.1 - 0.05);
}
}, [timescale]);
}
return data.reverse();
}

// get tolerances
const [tolerances, setTolerances] = useState({});
useEffect(() => {
const q_tolerances = query(
collection(db, 'tolerances')
)
const unsubscribe = onSnapshot(q_tolerances, (snapshot) => {
setTolerances(convertTolerancesSnapshot(snapshot))
});
return() => {
unsubscribe();
export function useTrackStats(timescale) {
const stats = generateMockData(12, {
TDS: 212,
air_temp: 25,
distance: 49.8,
humidity: 73,
pH: 8.1,
water_temp: 25,
dissolved_oxygen: 7
});
console.log(stats);
return {
loading: false,
stats: stats,
tolerances: {
"TDS": { min: 100, max: 300 },
"air_temp": { min: 20, max: 30 },
"distance": { min: 0, max: 98 },
"humidity": { min: 0, max: 95 },
"pH": { min: 6, max: 8 },
"water_temp": { min: 20, max: 30 },
"dissolved_oxygen": { min: 5, max: 9 },
}
}, []);

return { loading, stats, tolerances };
};
}

/*
Expand Down
Binary file added src/Lib/logos.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
150 changes: 69 additions & 81 deletions src/Pages/Home.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,15 @@ const team = require("../Lib/team.webp");
const esw = require("../Lib/esw.webp");
const wildIdeas = require("../Lib/wild-ideas.webp");
const msab = require("../Lib/msab.webp");
const logos = require("../Lib/logos.png");

const Home = () => {
return (
<Stack direction="column" spacing={3}>
<Stack direction={{ xs: "column-reverse", md: "row" }} alignItems="center">
<img style={{ width: "50vw" }} src={systemCAD} alt="System CAD"/>
<Box sx={{color: "text.primary"}}>
<Typography variant="h1"><TypedText string={"Northwestern AutoAquaponics"} typeSpeed={40}/></Typography>
<img style={{ width: "50vw" }} src={systemCAD} alt="System CAD" />
<Box sx={{ color: "text.primary" }}>
<Typography variant="h1"><TypedText string={"Northwestern AutoAquaponics"} typeSpeed={40} /></Typography>
<Typography variant="body1">
A fully automated aquaponic system that grows both fish and plants
unattended for months and can be <Link component={RouterLink} to="/dashboard">monitored</Link> and <Link component={RouterLink} to="/control-panel">controlled</Link> remotely
Expand All @@ -45,98 +46,85 @@ const Home = () => {
</Stack>
<Stack direction="column" spacing={3}>

<AnimationOnScroll animateIn="animate__fadeInLeftBig" duration={1} animateOnce>
<AboutSection
title="Mechanical Design"
images={[plumbing, sump, flow, filter]}
image_left={true}
<AnimationOnScroll animateIn="animate__fadeInLeftBig" duration={1} animateOnce>
<AboutSection
title="Mechanical Design"
images={[plumbing, sump, flow, filter]}
image_left={true}
>
Our plumbing system serves as the backbone of AutoAquaponics and facilitates the efficient
transport of nutrients to the plants. It also removes solid waste and potentially toxic
contaminants from the fish tank to ensure the health of our fish. AutoAquaponics’ plumbing
consists of a 100 gallon fish tank, two 40 gallon grow beds, a 60 gallon sump tank, and a
three-stage filtration system. The filter system includes a settling tank, a membrane
filtration tank, and a biofilm reactor to convert ammonia from fish waste into nitrate for
the plants.
</AboutSection>
</AnimationOnScroll>
Our plumbing system serves as the backbone of AutoAquaponics and facilitates the efficient
transport of nutrients to the plants. It also removes solid waste and potentially toxic
contaminants from the fish tank to ensure the health of our fish. AutoAquaponics’ plumbing
consists of a 100 gallon fish tank, two 40 gallon grow beds, a 60 gallon sump tank, and a
three-stage filtration system. The filter system includes a settling tank, a membrane
filtration tank, and a biofilm reactor to convert ammonia from fish waste into nitrate for
the plants.
</AboutSection>
</AnimationOnScroll>


<AnimationOnScroll animateIn="animate__fadeInRightBig" duration={1} animateOnce>
<AboutSection
title="Electronics"
images={[sensor, rpi, outlet, motorized]}
image_left={false}
>
These electronics put the Auto in AutoAquaponics, and allow us to both monitor and control
the system from anywhere in the world. Our electrical system currently features a smart
outlet box powered by an ESP32 microcontroller that automatically toggles the lights and
motorized ball valves in the system, allowing us to flood our two grow beds at different
intervals and control how much light our plants get. The outlet box also communicates with
our sensor box via Bluetooth Low Energy. The sensor box contains a Raspberry Pi and a
number of environmental sensors (pH, TDS, air/water temperature, relative humidity, water
level, and dissolved oxygen) to help us understand the state of the system in real time. The Raspberry Pi pushes
the sensor data to a remote Firebase database, which can be seen on the Dashboard page.
</AboutSection>
</AnimationOnScroll>

<AnimationOnScroll animateIn="animate__fadeInLeftBig" duration={1} animateOnce>
<AboutSection
title="Software"
images={[software1, videoStream, code, software2]}
image_left={true}
>
AutoAquaponics’ software allows users to monitor and control the system remotely, and it
uses Python and C++ to run the electronics that interact with our physical system. Our
platform is built with React and Google Firebase, and it features a live dashboard that
users can look at to monitor AutoAquaponics’ current state. The graphs are interactive,
and toggle to display up to 7 days of data at once. Some future developments on the
software side include creating the Control Panel, Settings, and Video Stream pages.
Control Panel and Settings will enable people with the appropriate credentials to receive
automated email/text updates on the system and toggle our smart outlet box to change
lighting/flooding durations. Video Stream will include a live stream video of our fish
that anyone can access. Stay tuned for more!
</AboutSection>
</AnimationOnScroll>
<AnimationOnScroll animateIn="animate__fadeInRightBig" duration={1} animateOnce>
<AboutSection
title="Software and Electronics"
images={[sensor, rpi, outlet, code]}
image_left={false}
>
<p>
The software and electronics put the Auto in AutoAquaponics, and allow us to both monitor and control
the system from anywhere in the world. Our electrical system features a smart
outlet box driven by an ESP32 microcontroller that automatically toggles the lights and
motorized ball valves in the system, allowing us to flood our two grow beds at different
intervals and control how much light our plants get. A sensor box with a Raspberry Pi and a
number of environmental sensors (pH, TDS, air/water temperature, relative humidity, water
level, and dissolved oxygen) helps us understand the state of the system in real time.
Our platform is built with React and Google Firebase, and it features a live dashboard that
users can look at to monitor AutoAquaponics’ current state.
The entire system is configurable from the Control Panel and Settings, enabling people with the appropriate credentials to receive
automated email/text updates on the system and change settings to manage
lighting/flooding durations.
</p>
<img src={logos} style={{ width: "100%", marginTop: "20px" }} />
</AboutSection>
</AnimationOnScroll>

<AnimationOnScroll animateIn="animate__fadeInRightBig" duration={1} animateOnce>
<AboutSection
title="Biology"
images={[fish, topView, biofilm, plants]}
image_left={false}
>
Biology is an integral part of any farming system, and ours is no exception.
AutoAquaponics cycles nutrients between aquatic animals, microbes, and plants, all of
which interact in a symbiotic manner. Food starts out in the fish tank, where it is
consumed and transformed into solid waste by the fish. This waste gets broken down by the
heterotrophic bacteria in the filters and turns into ammonia. The ammonia in the water
then gets captured by the nitrifying microbes, transforming it into nitrite and then
nitrate. This final product becomes the fertilizer that our plants uptake through their
roots to make protein and grow. We are currently cultivating wheatgrass, kale, and basil
in our grow beds, and our fish tank is stocked with tiger barbs, mollies, South American
cichlids, a Raphael catfish, and a red tailed shark. We also have many invertebrates
living in the system (ramshorn snails, cherry shrimps, and gammarus pulex) to serve as
live food sources for the fish.
</AboutSection>
</AnimationOnScroll>
<AnimationOnScroll animateIn="animate__fadeInRightBig" duration={1} animateOnce>
<AboutSection
title="Biology"
images={[fish, topView, biofilm, plants]}
image_left={true}
>
Biology is an integral part of any farming system, and ours is no exception.
AutoAquaponics cycles nutrients between aquatic animals, microbes, and plants, all of
which interact in a symbiotic manner. Food starts out in the fish tank, where it is
consumed and transformed into solid waste by the fish. This waste gets broken down by the
heterotrophic bacteria in the filters and turns into ammonia. The ammonia in the water
then gets captured by the nitrifying microbes, transforming it into nitrite and then
nitrate. This final product becomes the fertilizer that our plants uptake through their
roots to make protein and grow. We are currently cultivating wheatgrass, kale, and basil
in our grow beds, and our fish tank is stocked with tiger barbs, mollies, South American
cichlids, a Raphael catfish, and a red tailed shark. We also have many invertebrates
living in the system (ramshorn snails, cherry shrimps, and gammarus pulex) to serve as
live food sources for the fish.
</AboutSection>
</AnimationOnScroll>
<AboutSection
title="Contact Us"
images={[team]}
image_left={true}
image_left={false}
>
Send us a message: <Link target='_blank' href='mailto:[email protected]'>[email protected]</Link>.
<br/>
<br />
See our Project Blog <Link target='_blank' href='https://eswprojects.planio.com/projects/autoaquaponics'>here</Link>.
<br/>
<br />
Download or contribute to our code on <Link target='_blank' href='https://github.com/ESW-NU/AutoAquaponics-v2.0/tree/deploy'>GitHub</Link>.
<br/><br/>
<br /><br />
This project was founded under <Link className='NU' target='_blank' href='https://esw-nu.github.io/'>Engineers for a Sustainable World @ Northwestern</Link>
<br/><br/>
<br /><br />
<i>Made possible with the support of</i>
<Grid container direction="row" justifyContent="space-evenly">
<img css={{ maxHeight: 60 }} src={esw} alt="Engineers for a Sustainable World"/>
<img css={{ maxHeight: 60 }} src={wildIdeas} alt="Wild Ideas"/>
<img css={{ maxHeight: 60 }} src={msab} alt="McCormick Student Advisory Board"/>
<img css={{ maxHeight: 60 }} src={esw} alt="Engineers for a Sustainable World" />
<img css={{ maxHeight: 60 }} src={wildIdeas} alt="Wild Ideas" />
<img css={{ maxHeight: 60 }} src={msab} alt="McCormick Student Advisory Board" />
</Grid>
</AboutSection>
</Stack>
Expand Down