And it's even better that it's for a good cause as well.
Although, I dread to think what sort of files one would get when user uploads are allowed.
- Commissioner Pravin Lal, Datalinks
Alpha Centauri pertinent as ever.
"Since the device is a light bulb, it would be difficult to detect and likely to go unnoticed."
I doubt it would be any harder to shut down than any other public-access WiFi device, just a bit of experimentation with turning off power / devices would find it.
I can't wait until it's formalized enough that I can just buy a $20 light bulb, update it wirelessly somehow, and then have my own little "light bulb library" server.
Reads like you had fun, keep up the hacking!
P.S main -> mail I think?
1: > I was talking with a friend about this idea and the storage limitation and he thought it would be cool to have these devices form a mesh network
2: https://meshtastic.org/docs/community/enclosures/rak/harbor-...
Now where the USA censors routinely is financial censorship. If you can afford the thing thats fincially banned, the sure, its not banned. But if you cant afford it, youre screwed.
And, if you work for a company, they can fire you for any/no reason, INCLUDING your speech off work.
In the USA, its "freedom of speech" if youre independently wealthy. If not, hope you dont offend power.
Very cool project nonetheless!
I would put Kevin MacDonald's antisemitic trilogy The Culture of Critique, the Turner Diaries ( which calls for mass extermination of non-white groups in the USA ) and Mein Kampf in the realm of books that should be shunned.
Best 4x game of all time. The 2060 that game envisioned seems closer everyday.
Sister Miriam Godwinson, We Must Dissent
This game and its ideas are so timeless.
LoRa is also sub-optimal for payloads more than a few K in size and most ePub files are at least a meg...
Take a look at HaLow if you insist, but in general if a bulb has esp32 then you could likely replace the module for one with LoRA capabilities.
I’ll grant that some of the restrictions seem overprotective. That being said, a parent could easily check out one of those books for their child.
Purely obscene material is also not protected by the 1st, but since the 1970s, the bar for that has been placed very, very high.
The closest I can think of offhand is that for about a year during the pandemic, Twitter suppressed gratuitous COVID misinformation posts, at the request of the government.
When people say "banned book" they mean that a certain level of government such as a school board or municipality has "banned" them from being in a public (often school) library.
But the headline "In [state I disagree with] they are banning books that have [ideas I agree with]" makes a lot more headlines and clicks.
Then people run with the phrase "banned books" to make things sound worse than they are.
The book that is commonly at the number one spot on "banned book" lists has what would always be called hardcore pornography in the middle of the book. It depicts fellatio literally (not just implying it). It has no educational value, and is meant, within its context, to be erotic/lewd. I can link directly to it on archive.org, I can link to that exact page even. I do that sometimes in these arguments, and I'm downvoted until my comment is hidden but not before a bunch of jackasses say "and what does it matter"...
Sorry, don't want my 10 yr old looking at it in the school library. No, take that back... I'm not sorry. And you're all awful people for wanting that in the school library. Or dumb for not realizing that it's in the book. What I've come to realize as I've gotten older, is that some people think they have a right to show smut to my young children behind my back and want to call me a Nazi if I object.
all 4 of the books that are checked-in to that repo are old enough that they're in the public domain. I looked at Call of the Wild and it has a title page saying it came from Project Gutenberg, I assume the other 3 likely did too.
rather than jumping to conclusions about the author being influenced by a "psyop" I think there's a much simpler and more boring explanation - they didn't want to check copyrighted ebooks into a publicly-accessible Codeberg repo.
The answer should be obvious: it would be a white supremacist library.
Given that the present administration includes fans of those books, their banning seems unlikely. Perhaps a refresher on the kinds of books that are presently under threat is in order? https://www.ala.org/bbooks/frequentlychallengedbooks/top10
(You can find contemporary Huck Finn censorship attempts in their database here, by the way: https://airtable.com/appZthgrTU9u1Bf5d/shr4J8Mgiua2CV2Ig?mWW... )
I thought we would all be over this after the dr seuss thing.
>- Culture of Critique
>- The Turner Diaries
Actual banned books. So of course your comment is flagged. Groupthink censorship is still censorship.
"This is how democracy dies, with thunderous applause." feels more grounded in reality.
Peak of complexity and maturism in games...
https://www.theguardian.com/us-news/2026/apr/22/us-libraries...
It's been a while since I used the github gist 'download zip' functionality. Quite handy.
Some of us remember when they assured us that the novel virus in china was not to be afraid of.
Modern enterprise access points even have built-in functionality to physically locate devices, and automatic warnings for rogue access points. The latter is often ignored or disabled though, because it'll go off every time someone prints or screen casts over Wi-Fi Direct.
https://en.wikipedia.org/wiki/Book_banning_in_the_United_Sta...
USA is ultra conservative on the average in comparison to just any European state for example.
I *love* this concept so much.
Even though the books are a neat hook, these wifi networks could contain anything.
Grassroots political advocacy, local info for off-the-grid historical sites, location specific micro-social media (comments, message boards, etc.), waymarkers, geocaching, hidden music / art / games in obscure places, ARGs like an interactive capture the flag or something even more inventive and fun, ...
God, this is just so freaking cool and is begging for a thousand different ideas to run on top of it.
Good job! One of the best things I've seen all year.
If you have a problem with storing illegal books in your "banned book library", you may be working on the wrong project.
And your "historically banned" is just "occasionally removed from public school libraries on parental request". Not using tax money to promote them to children is a low, low bar for "banned". While actual availability is, of course, completely ignored. Whatever tells the best story, facts be damned.
[1] Jared Taylor's Banned Conference Speech - https://www.arktosjournal.com/p/jared-taylors-banned-confere...
[2] Ohio universities involve FBI in investigation of ‘It’s okay to be white’ and white nationalist group’s postings on campus - https://www.thefire.org/news/ohio-universities-involve-fbi-i...
Y'know, there's really only one reason to be coy about whether you agree with Neo-Nazi propaganda.
However I haven't actually played with this and don't know if that would work, or if the network would require DNS to function properly.
I understand your discomfort with these books, and I actually agree that they deserve to be banned, but banning is not what we should do.
Its ultra important for historical/social and linguistic education.
I read it when I was 14, and I'm from Poland.
It is AWFUL and PAINFUL to read due to the horrible styling - which amuses me to this day. :) That's why no need to ban it hehehe
Nowadays books are for intellectuals, not for the masses... That's why I would be ok for any modern teenager to read anything 18+ or anti-whatever books :) It's net positive no matter of content IMO.
I guess when it comes to Freedom of Speech, you fall into the latter category.
The thing is that every other country does have what they're describing.
> The Color Purple has been challenged many times in order to be removed from public library circulation and public school curriculums.
And yet nobody challenged it to get it removed from US Amazon. Amazon _is_ forbidden from selling certain books in other countries. It's so not the same thing
Straight to the ad hominem attack on taboo thoughts. Transparent. If the books are true what is your problem with them? Not me. Not the other poster. Not a strawman fallacy. What is your issue with the content of actually banned books? Be specific.
I found the strat to go was to spam Scouts for resources, then get that upgrade that gave you Science per deal and spam every cheap deal as soon as you can.
The majority of "banned books" are books that a random school district/religious school in a conservative part of the country elected not to include in their library at some point. Many of them are required reading in many other school districts and some of the most well known books of the 20th century.
The closer-to-banned ones are generally not included on banned-book-reading-lists and are banned on major retail platforms and long out of print and tend to be racist and/or genuinely subversive to liberal democratic principles. Most of these tend to be some of the most-downloaded-books-on-the-internet, and are also in no way illegal to own in the US - though possession of many is illegal in much of the EU.
An interesting case is United States vs Progressive inc [0] in which the US dropped a lawsuit to prevent a magazine from publishing a how-to guide on building an H bomb and Defense Distributed vs United States Department of State [1] in which the US federal government settled and allowed for the publishing of 3d printed gun files online, previously prevented under arms exports claims.
0: https://en.wikipedia.org/wiki/United_States_v._Progressive,_.... 1: https://en.wikipedia.org/wiki/Defense_Distributed_v._United_...
Having been in prison, I can tell you that being a Blood and having "certain books" in your locker is a "smash on sight" offense. The same could be said for the Aryan Brotherhood/Circle, and I'm sure for many other gangs.
There's a difference between "this one small group: local oklahoma school district/aryan brotherhood/catholic church" decided they don't like a book and the government level you will be imprisoned for owning/sharing this book.
If it's a 'banned' book library, why doesn't it include books banned by a variety of sources? To me, a 'banned' book library would included many thousands of books each tagged by which groups are banning them. That way, were I inclined to do so, I could read texts that were banned by both Jews and Christians, or by both democratic nations and totalitarian regimes, or whatever it was that I was interested in.
This particular compilation is a perfect example. Calling The Call of the Wild, a book that's been made in to several movies (the most recent of which grossing $111.1 million against a production budget of $125–150 million) a "banned book" is kind of ludicrous, no? Clearly many thousands or millions of people have access to it and it's contents, so it is clearly not 'banned' in any meaningful sense of the term, unless you happen to live in some region in which it is banned, but that enforces my claim that any such random small list doesn't really live up to the label.
Well. I seem to have triggered something.
> Ah yes, because the only people that have ever spread propaganda are Neo-Nazis
Not something I ever said or implied.
> and we should only ever learn about the sanitized and approved version of history from our Robert Maxwell (Ghislane Maxwell's Mossad agent father / McGraw Hill co-founder) published textbooks.
I find it interesting who just happens to know who else is Jewish, and then feels the need to interject that into utterly irrelevant contexts.
> Never mind that there are two sides to every story, and when it comes to history, only the victors get to tell theirs.
No, I'm pretty sure a lot of losers have been able to have their sides heard. It's just that, well, people lose for a reason, and losers tend to be less popular among normal people. Ranting about subhumans can do that, you know.
> We don't even learn about the 23+ million massacred by the Bolsheviks in school.
I would be interested to know who exactly you call a Bolshevik, but I did get taught the history of the USSR in school, at least, and "One Day In The Life Of Ivan Denisovich" is not a ringing endorsement.
However, nobody was talking about Bolsheviks until you decided to use them as a distraction.
> But yeah - only one reason to consider a different perspective other than the one forced down your throat by the public education system.
I didn't need the public education system to teach me to hate genocidal racists, thank you.
A library can choose what books they stock (especially a school library. Of course they're highly curated.). You don't have to agree with their choices, but the book isn't banned. You can still find it at a county library, an ebook library, or on the Internet.
So it's a bit dramatic to say "I'm fighting the system by hosting banned books!", just because some Tennessee elementary schoolers can't check it out from their school library. Just feels like a joke and a mockery when there's governments that genuinely censor books.
In the article example, to deny this because of a technically or the degree of legal enforcement is foolish since it is rebelling against the act of banning books, the process of banning, which doesn't occur out of thin air, it is an evolution of acts. It is not an absolute and one doesn't have to wait until there is a legally defined ban to start the protest. That would be ridiculous as it would be too late.
I don't think the project is trying to make the Banned Library of Congress either, anyone could put whatever books they want on their server. It is suggesting civil disobedience by circumventing oppression through censorship with creativity, which is awesome.
> Not something I ever said or implied.
No but you said there was only a single reason to agree with "Neo-Nazi" propaganda as if agreeing with any propaganda is rational. There's a reason it's called propaganda after all. It's not like there weren't deplorable crimes being committed by the Soviets / US / France / Britain and they certainly had their fair share of propaganda during WWI and WWII depicting Germans as barbarians / sub-human / etc...
> I find it interesting who just happens to know who else is Jewish, and then feels the need to interject that into utterly irrelevant contexts.
How is the owner of the second largest publisher of textbooks in the US, and the fact that he served in a Zionist intelligence organization in the US, irrelevant when it comes to what people learn about WWII and propaganda? Please explain.
> No, I'm pretty sure a lot of losers have been able to have their sides heard. It's just that, well, people lose for a reason, and losers tend to be less popular among normal people. Ranting about subhumans can do that, you know.
I've already addressed this above.
> I would be interested to know who exactly you call a Bolshevik, but I did get taught the history of the USSR in school, at least, and "One Day In The Life Of Ivan Denisovich" is not a ringing endorsement.
Are you disputing the well-recorded fact that tens of millions of innocents were killed by the Bolsheviks over the span of about 40 years? Why don't we learn about the Holodomor in the US in grade school? I don't need to explain who the Bolsheviks were - anyone who doesn't already know, can perform a basic internet search to figure that out.
> However, nobody was talking about Bolsheviks until you decided to use them as a distraction.
A distraction? How is me pointing out that history isn't objective and that there are two sides to every story, yet we only learn about one, a distraction? It's highly relevant to the conversation and the GP's post.
> I didn't need the public education system to teach me to hate genocidal racists, thank you.
I never said you did - but if it were me, I'd want to make sure I considered both sides of a historical event before deciding which direction to aim my hatred, if I was into such endeavors. I personally believe that war is a racket, and that there are no good guys in evil and corrupt wars (WWII was definitely one of those, same with WWI). I'm also not naive enough to believe that there wasn't atrocious behavior on both sides of either war. Nor am I going to label anyone who has the gall to question the prevailing narrative or say it is incorrect in some capacity, a Neo-Nazi.
Those of us who paid attention certainly did.
Go on then.
Say what you mean.
Of course they aren't. But that's no argument for distributing it.
every time some random school in bumfuck Alabama removes some LGBT/CRT/DEI/ESG/whatever pamphlet no one was ever going to actually read from its library, every dailybeast/salon/huffingtonpost/motherjones/etc equate that incident to Nazi book burnings. if you want, I can don a hazmat, venture to r/politics, and exhume a dozen threads about such incidents where the target audience of those articles expresses their anger and disappointment over hundreds of near identical comments.
There's an alternate reality where white supremacy is mainstream, where queer fiction is impossible to find, and that would be a different world.
Instead, what's being preserved are the books written that celebrate the values that match our broad cultural values, despite a handful of cultural deviants attempting to suppress the parts of the rest of humanity they dislike.
Edit: per the other comment's Wikipedia link, the unredacted Operation Dark Heart seems banned in the US because it included classified info
It’s one of those things we will never get perfect but I find we generally do it well enough to continue advocating for it. The problems start when people with extremely polarized beliefs fixate on marginalized groups that are not actually causing issues (transphobia), or they find something incredibly fringe then deliberately twist it and use it for fear mongering (satanic panic). Neither is done in good faith. They’re looking for a tool to project their moral frameworks in an aggressive, invasive manner.
“Live and live” is a pretty good role of thumb to live by for most people in most cases
We're only talking about one political group here. The group that published The Turner Diaries. The group that can't help but mention who's Jewish. Bringing up other groups is a distraction tactic, aside from how dishonest it is. Yes, we are taught that everyone did morally questionable things in WWII. But only one group ran a Dachau.
> How is the owner of the second largest publisher of textbooks in the US, and the fact that he served in a Zionist intelligence organization in the US, irrelevant when it comes to what people learn about WWII and propaganda? Please explain.
OK, let's get down to brass tacks: Do you think people only believe the Holocaust happened and was bad because a Jewish man published a lot of textbooks?
> Are you disputing the well-recorded fact that tens of millions of innocents were killed by the Bolsheviks over the span of about 40 years?
Are you disputing the fact eleven million people were killed by a concerted effort on the part of Nazi Germany to eliminate people it considered subhuman for various reasons?
I don't dispute the vile stain on the history of state Communism. I hate Stalin and Mao and Pol Pot and Hoxha and Kim Il-Sung just as much as the next normal person. But we're talking about why someone wouldn't distribute The Turner Diaries and, I have to say, the Communists didn't commit that little literary peccadillo.
> Why don't we learn about the Holodomor in the US in grade school?
Because there are fewer people waving hammer-and-sickle flags around than there are spray-painting swastikas on synagogues and Raising Questions about whether the Holocaust was so bad after all.
> I never said you did - but if it were me, I'd want to make sure I considered both sides of a historical event before deciding which direction to aim my hatred, if I was into such endeavors.
That's funny, the more I learn about WWII the less I feel the Nazis had a legitimate side. They were a bunch of losers lead around by a drugged-up corporal who ran his country into the ground with gross mismanagement to the point Germany, once the jewel of European science and industry, was split in half and lived a shadow existence as the puppet of two world powers for a half century after his reign.
> I personally believe that war is a racket, and that there are no good guys in evil and corrupt wars (WWII was definitely one of those, same with WWI).
The corruption in WWII was the starting of it, which falls directly at the feet of the Nazis and Imperial Japan. Self-defense is not corruption, and neither is ending the reign of expansionist tyrants. Or do you think people don't have the right to defend themselves from your pet dictators?
> I'm also not naive enough to believe that there wasn't atrocious behavior on both sides of either war.
Only one side ran death camps. Both sides imprisoned people unjustly, but only one side turned them into ashes. It doesn't balance out.
> Nor am I going to label anyone who has the gall to question the prevailing narrative or say it is incorrect in some capacity, a Neo-Nazi.
No, the only people I call Neo-Nazis are the ones triggered when I say the Nazis were, on the whole, bad for everyone around them.
If that simple, easily checkable fact doesn't get your hackles up I would know why that it doesn't.
Also, the books on the bulb include Huckleberry Finn, which was removed from required reading in some Democrat-governed California cities because it uses racial slurs.
Although copyright-infringing books may be illegal to redistribute in general, the difficulty in determining what counts as copyright infringement and what counts as fair use means you can't really tell for sure which books are illegal to distribute and which aren't, so I'm not sure that really counts as "banned". 60 Years Later had an actual court order which makes things a little more concrete.
That doesn’t mean Nazi Germany wasn’t utterly disgusting though.
And how does that differ from including banned books?
It's a mouthful, but at least you'll be honest for once.
The FBI investigating a bombing? Yeah.
State cops investigating a murder? 50/50 odds?
Local cops investigating someone swapping out a fucking lightbulb? No.
This is a perfectly sensible set of sample and example books. The project is the book distribution system, not the books themselves.
You load yours up with whatever you think is important or whatever you are willing to risk.
That's a significant impugnment of the honesty of a person you know nothing about.
"Banned books" is the colloquial term for these books, even if it's not as accurate as you'd personally prefer.
Next thing you'll be complaining you bit into an Apple and got computer instead of fruit.
Yes, many people are either unaware of, or willing participants in, this lie. That doesn't make it any less of a lie.
that was the only point I was making. Mein Kraft, Selected Works of Lmao The Dong, and The Anarchist Cookbook may be removed from sale/access in some specific locations, but it is very much legal to buy, own, and sell them.
You're just pissy because they aren't using your personal favorite parameters around "banned" for "by whom" and "for whom". You're pretending your opinion is fact and therefore anyone who disagrees must be a liar.
We no longer say that "cannabis is illegal in California"; that would be factually incorrect. Instead we say, "cannabis was formerly illegal". In standard usage of English, the same pattern applies to banned vs formerly banned books.
Edit: wording
People inside the oppressive regime will unwittingly buy smart bulbs, that only activate after enough were smuggled in at the same date, so that by the time the regime detects some, all bulbs will be traced to non-refugee sellers outside its jurisdiction, absolving any unwitting participants buying and powering them, so it's important the ad doesn't advertise any quirks or functionality added, as that would compromise the buyers.
By having the sellers be random foreigners (from the perspective of the oppressive regime), the regime can't punish the family of the refugees sponsoring this infiltration, if it doesn't know which refugee friends the seller has (so it should also be a low contact friend, so the refugee would have to convince a friend to do one large batch once, and never meet again..., which is a bit sad).
This assumes commercial entities aren't selling friend network data, or if they do, that oppressive regimes somehow can't get their hands on it. A rather dubious assumption in 2026...
It deliberately conveys an impression that is opposite of the truth. But feel free to continue to split hairs and twist words to argue that technically you're not actually lying.
And even in high schools screening R-rated movies is generally heavily restricted. Where it is allowed, it generally requires a permission slip from the parents. And that's not like showing gratuitous films, but ones with historically relevant and educational context like e.g. Schindler's List.
So why is it unreasonable for parents (or other interested parties) to be against having such material in a children's library? In many ways its quite odd that a rating system was never adopted for books. And for one other question, do you even see a difference between these sort of books being restricted from schools, and other books whose content would be generally be rated appropriate for children, being banned on political/ideological basis? Because to me the difference is not only tremendous but the defining issue here.
Just because you decided to interpret something one way, doesn't mean it was a deliberate choice by the other party, nor does it mean your interpretation is common.
> technically you're not actually lying.
What did I say that you consider a lie? Could you quote me?
"My" list? I don't have a list. If you're talking about OP's list, I disagree with that. Tom Sawyer and Huckleberry Finn are not sexual books. From what I know of many of the other more recent books people have tried to ban, I absolutely would not agree the ratings would be R/NC17 for those either. Here's a list of PG13 (And PG!) movies with nudity. https://www.imdb.com/list/ls548607223/
I agree that 7 year olds should not be shown sexual content. There exists content with sexual themes which are appropriate for teenagers.
Also, the comment you replied to is downthread in an argument over whether it's a "lie" to call books that someone banned, a banned book.
The top book on that 'challenged' list is a diary style book of a 13 year old girl who is sold into sex slavery, repeatedly raped, abused, and so on - with descriptions of each 'encounter.' The book is meant to be, and indeed is, extremely disturbing as it's part of the author's activism against sex trafficking throughout the world - as it was based on real events as per her research. In any case, it most certainly is not PG13 by any stretch of the imagination. It's much closer to something like Requiem for a Dream than it is to Tomb Raider.
I also think that's a great book to have on top, because it emphasizes that the issue isn't ideological, but content. I think there's few people who would claim Sold doesn't have tremendous value, or that it should be banned. But there's going to be a lot of people that don't want books like that anywhere near children. And for good reason - it's the same reason I wouldn't really care if my kids wanted to watch Total Recall or Terminator, but no way would I let them watch Requiem for a Dream. The violence and triple titted aliens of Total Recall are borderline comical, but I don't think stuff that gives you that awful 'ughhhh' feeling like Requiem for a Dream (or indeed - Sold) is something that's going to be at all healthy for a child's development.

A long while back I had an idea to hack a WiFi smart light bulb to do something more useful to me. Actually, I had a few different ideas of things to do with them. One of these ideas was to modify the device to have an open WiFi access point and a web server hosting banned books. The idea was that if you lived somewhere that banned books you thought were important, you could theoretically stick a digital copy of the book on one of these light bulbs. Then you could go install it somewhere in your community. As long as the light bulb is switched on, then anyone in the vicinity can still access the banned material assuming they have an electronic device with WiFi. Since the device is a light bulb, it would be difficult to detect and likely to go unnoticed. A cyberpunk digital dead drop. These devices are also fairly inexpensive, so leaving them around town as is hopefully not very cost prohibitive.
I think the idea hosting banned books specifically came to me after having read Ben Brown's short story Library. It's been a while since I read it, but if I recall there are characters in the story who maintain a "library" which acts as a digital archive of creative works, owners manuals, 3d models, etc. Things that others might find useful or interesting that you wouldn't want to lose should they be somehow wiped from the Internet. That's only a part of the story and it was a fun read. You should go read it!
Anyway, a few months ago I decided to finally get to work on this project. The result is the Banned Book Library!
I brought up this idea with some folks at my local DEFCON meetup group. One of them had some experience with home automation and recommended I look into Tasmota. Tasmota is an open-source firmware you can install on various smart devices to integrate them into a home automation system such as HomeAssistant. The main idea with this firmware is to provide you with local control over the device. Many of these devices rely on cloud services that change over time or sometimes completely disappear, leaving the devices unusable. Tasmota allows you to untether yourself from these cloud services and host everything internally. Actually, this is another great parallel to Ben Brown's Library story. Also relevant is Cory Doctorow's Unauthorized Bread.
I hadn't heard of Tasmota but after reading about it, it sounded like a good way to go. I had sort of expected many of these smart light bulbs would rely on ESP32 chips, or similar. Having no experience with them made it feel a bit daunting to get started. I thought maybe it might be easier to modify the Tasmota firmware to do what I wanted instead of writing something from scratch. I did not end up modifying Tasmota in the end, but this rabbit hole did lead me to find a website that sells WiFi light bulbs with Tasmota pre-installed. The product page even specified that the bulb uses an ESP32C3 4MB. It also listed which GPIO pins were used to control the various LEDs, which would come in handy later:
R:GPIO6
G:GPIO7
B:GPIO5
CW:GPIO3
WW:GPIO4
This seemed like a great starting point because although Tasmota supports many other devices, not all of them can be flashed over the air (OTA). Many of them require breaking them open, soldering on small wires, and flashing via a serial programmer. Tasmota has a built-in mechanism to update the firmware OTA, so it seemed likely I might be able to flash my own modified Tasmota firmware, or otherwise a custom firmware without having to tear the light bulbs apart.
The one thing that struck me as a potential problem was the flash size. It was listed as 4MB. This is not very much space to host a library of books... That 4MB would need to fit all of the firmware, the website, and any books. Not much space. I thought I might be able to overcome this by adding storage, such as a microSD card reader. More on that later.
I purchased two of these bulbs to play with. I figured I might end up breaking or bricking one, so having a backup would be good.
The bulbs showed up in the main a few days later and I opened up the box to check it out.

The first thing I wanted to do was open it up and see what I was working with. I was mainly wondering if the pins were exposed so I might be able to attach a microSD card reader. To remove the white plastic bulb on top, I ran a razer blade around the circumference of the bulb in between the base and the bulb. I had to go around twice, the second time angling the knife downward to cut through the sealant inside. Then I was able to just twist and pull the bulb right off. Minimal damage.

This revealed a round daughter board with all of the LEDs on it. This PCB was attached to another one underneath using six pins. There was also a hole in the middle where the mother board stuck through a bit. This ended up being the antenna for the ESP32. The bulb housing was lined with aluminum and the daughter board was also made of aluminum. So they likely designed it this way to ensure a decent wifi signal.
The daughter board was glued in with more sealant. I used my knife to cut through this and a small, flat screwdriver to carefully pry the daughter board out. I slid it up so it would separate from the mother board.

Now I could very clearly see the ESP32C3 inside, as well as some other supporting circuitry. I'm no electronics expert, but I believe most of the components inside are to convert the AC mains power to a cleaner 3.3V DC for the ESP32 as well as whatever voltage was needed to drive the LEDs. I never plugged this device into mains while it was open so I didn't measure the voltage for the LEDs.
One nice thing about this ESP32 was that it seemed to have a bunch of pins exposed. I hoped this might make it possible to solder on a microSD card reader for expanded storage. You can also see some of the pins are labeled at the bottom to let you know which pins are for which colors.

There was really no way to get a soldering iron inside the bulb. The ESP32 only had the antenna portion sticking out above the housing. The only way I was going to solder any wires to those pins was to remove the mother board. Unfortunately this was not a simple task. The mother board was held in place with some kind of rubbery potting compound. There was... a lot of it. I had to dig it out with a knife and screwdriver and then yank the board out.

I chipped away a bunch of the compound from the mother board to get a better look at it. It made a mess.

This was a huge pain and not something I would want to be a required step in the process of setting up one of these dead drops. I really wanted this project to be as accessible as possible, requiring minimal tools and hardware skills. Not only that, but there really was no way I was going to get this re-installed properly. And even if I managed to get it back in, I wouldn't trust it to be safe. It could become a fire hazard for all I know.
All that said, this did give me a bit of a development platform to work from. I thought since I had this thing apart anyway, I might as well solder on some wires for serial programming. I had not done this before, so I had to do some reading to figure it out. Basically, I needed to power the chip with 3.3v and ground. Plus I need one wire each for the serial UART TX and RX pins. The first question to answer was which pins were the right pins?
I managed to find this exact module on AliExpress. The listing included an image of the backside of the module, which thankfully had labeled all of the pins.

This helped me figure out the VCC, GND, TX, and RX pins. For GND I ended up just soldering to the metal shielding as it was also grounded and much easier to solder to. I soldered wires to all of the other pins. I had to remove a few capacitors in order to get in there.

In order to program the device via serial, you have to boot it into a special download mode. This seems to normally involve shorting one of the ESP32 pins to ground while it is powered on. I can't remember how I figured this out, but it ended up bing the IO9 pin in this case. So I soldered another wire there.
I set my bench top power supply for 3.3v and hooked it up to the chip. Applying power to the VCC and GND wires did boot it up and I could see the IoTorerro access point waiting for me to connect and configure the device.
To get it into download mode, I powered the device off. I connected my FTDI device to my laptop's USB port and then to the GND, TX, and RX wires on the mother board. Then I manually shorted the IO9 wire to the shielding to ground it. Then I powered the device on. I could see this time it was only drawing about 0.09 amps, which was much less than before. So something was different.
I thought the first thing I should probably do is try to dump the entire firmware. This would hopefully allow me to flash it back to the device to start it back over from a clean state. I used esptool to do this.
esptool --chip esp32c3 --port /dev/ttyUSB0 --baud 114200 read-flash 0x0 0x4000000 ./tasmota_original_firmware.bin
I could see that it was able to talk to the device and after a few minutes I had a firmware dump! Things were looking good so far.
Early on I went looking at the Tasmota source code to see if I could modify it to act as the Banned Book Library. The firmware was much more complicated than I had anticipated. Not only that but it supported all different architectures and devices. It also had many features I really didn't need. And considering the purpose of my project, I wanted to try and keep the firmware bloat down to make more space for book storage. So I scrapped the idea of modifying Tasmota.
I then discovered that you can program ESP32 devices with Arduino. I had used Arduino a bunch maybe 10-15 years ago, so I had some experience with it. I recalled it being pretty accessible and making it easier to work with embedded systems. But I was definitely rusty and I had never used it to program an ESP32 before.
I setup the Arduino IDE on my laptop and configured it to use my ttyUSB0 serial programmer as well as the proper ESP32C3 chip. I then wrote a very basic hello world program to just send a message over the serial port back to the laptop. This would let me test and see if I could flash the firmware and get the device to do something new.
I used the Arduino IDE's built in upload feature to upload the code to the device. It took care of the complicated stuff and just did it. I checked the serial monitor and found that it was working! I did get serial output from the device. So I was able to write my own firmware to this thing.
The next thing I wanted to do was setup an open WiFi access point and Web Server. I believe I started with this tutorial to get an idea of what to do, though I modified it since I wasn't interested in controlling an LED at the time. I later switched to using Async Web Server and used this tutorial to get a handle on things.
After getting that working I wanted to try and get a microSD card working. I purchased some breakout boards from Sparkfun.
I went reading the ESP32C3 datasheet to figure out how to wire up the SD card reader. I managed to figure it out eventually. However, instead of soldering to this device, I decided to switch to using an Adafruit ItsyBitsy ESP32 that I had laying around unused from a previous project. The ItsyBitsy was easier to work with because it breaks out all of the pins in such a way that I could solder on header pins. This made it really easy to attach the microSD card reader for prototyping.
Then I followed this other tutorial to figure out how to program the ESP32 to use the device. I did end up getting this to work and even using it to host files for the web server using LittleFS, however the entire idea of adding a microSD did not work out so I won't go into detail on this.
The real problem with the microSD card idea was that soldering wires onto this ESP32C3 in the actual device was a real pain. There was no way to do it without removing the board from the housing which effectively destroys the device as far as I'm concerned. I tried to get creative.
First I looked at repurposing some of the LED controller pins. There were six pins going from the mother board to the daughter board. Five of those were for the various LED colors: warm white, cool white, red, green, and blue. I didn't care about the RGB at all. And I could do away with either the warm or cool white if needed. However this didn't pan out. The way this device is designed, the mother board sends power to the daughter board via one pin. The other five pins route back to transistors on the mother board. The ESP32 turns its GPIO pins high which then triggers the transistors and completes the circuit for each color back to ground. This meant that the GPIO pins could only be used for output at best in this configuration. No input.
I then had a crazy idea to make a "clamp" that could clamp onto the top of the ESP32 and possibly allow some header pins to make contact with the exposd ESP32 pins. I designed a small 3D-printable part that was intended to slip over the ESP32C3 and clamp into place.

This ended up being way too finicky and unreliable. After several iterations of the design, I abandoned the idea altogether.
At this point, I decided to try looking at some other bulbs. Maybe there were other devices out there that would lend themselves better to soldering on some extra components? The problem was I didn't want to break the bank buying 20 different LED bulbs just to see if the idea was even feasible. I started by looking into prior research. I found several teardown articles featuring various smart light bulbs, but they all looked very similar to my setup. It did reveal that they don't all use ESP32. At this point I had decided I only wanted to stick with the ESP32 since I had already spent time learning how to program it.
I bought a few bulbs from the local hardware store. One of them had a similar design, but actually had a bit of aluminum protecting the mother board that I couldn't remove safely.


The Philips WiZ looked promising at first. It used an ESP32C3-mini-1 and the entire chip was exposed after removing only the plastic bulb!

Unfortunately none of the ESP32 pins were accessible on this module. So there was no way to solder wires for anything I needed.
I also tore apart a few standard LED bulbs with no "smart" components. I thought maybe I could just stick my own circuit inside. But this ended up looking more complicated and specialized than just flashing the Tasmota bulb.
There was also an interesting DIY LED smart bulb project I found on Hackaday that intrigued me, but I really preferred the idea of repurposing an off-the-shelf unit.
Ultimately I decided to stick with the Tasmota bulb and to just try to work within my 4MB limitations.
To get a handle on the storage situation, we can look at the ESP32 partition table. The table is normally stored at offset 0x8000 in flash, so we can dump this section and then convert the binary to a readable CSV file.
$ esptool -p /dev/ttyUSB0 --baud 115200 read_flash 0x8000 0x1000 part_dump.bin
Warning: Deprecated: Command 'read_flash' is deprecated. Use 'read-flash' instead.
esptool v5.1.0
Connected to ESP32-C3 on /dev/ttyUSB0:
Chip type: ESP32-C3 (QFN32) (revision v0.4)
Features: Wi-Fi, BT 5 (LE), Single Core, 160MHz, Embedded Flash 4MB (XMC)
Crystal frequency: 40MHz
MAC: 0c:4e:a0:31:cb:e4
Stub flasher is already running. No upload is necessary.
Configuring flash size...
Read 4096 bytes from 0x00008000 in 0.4 seconds (87.2 kbit/s) to 'part_dump.bin'.
Hard resetting via RTS pin...
I used gen_Esp32part.py to generate a csv file describing the partitions.
$ gen_esp32part.py part_dump.bin
Parsing binary partition input...
Verifying table...
# ESP-IDF Partition Table
# Name, Type, SubType, Offset, Size, Flags
nvs,data,nvs,0x9000,20K,
otadata,data,ota,0xe000,8K,
safeboot,app,factory,0x10000,832K,
app0,app,ota_0,0xe0000,2880K,
spiffs,data,spiffs,0x3b0000,320K,
This revealed five partitions:
As I went through this project, I eventually learned that nvs is used for non-volatile storage. This is where the main firmware can store configuration settings like the WiFi network, password, LED color, etc. That way when it reboots, it can remember these settings.
I'm not sure what otadata is used for exactly, other than it has something to do with over the air updates.
The safeboot partition is a second bootable firmware that Tasmota uses to flash the main firmware. It seems that the usual way of dealing with OTA updates is to have two duplicate firmware partitions of the same size. You boot from partition A, and then when you install an update it gets written to partition B. Then you reboot into partition B. If everything looks fine, the firmware can then be flashed to partition A. This way if a firmware update fails on partition B, the device can recover by rebooting into partition A. The downside with this method is that you need to firmware partitions of equal size. This takes up a lot of space.
Tasmota does things a bit differently in the safeboot configuration. Instead of having two duplicate firmware images, there is the main firmware stored in the app0 partition. It then has a second, smaller firmware stored in safeboot. The safeboot firmware can connect to a pre-configured WiFi network and flash the app0 partition and that's about it as far as I can tell. You can't even use the safeboot firmware to configure WiFi. That must be done via the main firmware. Safeboot must read the settings from nvs. The benefit of doing things this way is that the main Tasmota firmware can be larger with more features without taking up double the space for OTA updates. More info on this can be found here.
Finally there is the spiffs partition. spiffs is a file system type but in this case can also represent a more modern LittleFS file system. It's basically a small partition to store files.
With this configuration, the main firmware had close to 3MB of space and the safeboot was close to 1MB. There was just 320K for storage. That might fit one ebook, depending on the length. Not ideal.
It occurred to me that I likely didn't need 2880KB to store my own firmware since mine would be much simpler than Tasmota. I thought I might be able to adjust the partition size from in the firmware itself to shrink the app0 partition and grow the spiffs partition. That would give more space for web files and books.
I eventually did figure out there was a way to do this thanks to this blog post.
Editing the partition table is risky because if it gets corrupted the device may not boot and would only be recoverable via serial programming. This is not ideal, but the whole project is a hack so I guess what the hell?
The partition table is stored at offset 0x8000 in flash memory. So really all we need to do is overwrite the table with whatever we want it to be. We can't just change the partition offsets and sizes though, because there is an MD5 checksum value at the end of the table data. Therefore we would need to update this value as well or the device will not boot. We also can't move the app0 partition while we are booted into that partition or else we will not be able to boot back to this firwmare as it will not be lined up to the start of the partition.
I modified the partition.csv file to look how I wanted the partitions to look and saved it as partitions.csv.new:
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
safeboot, app, ota_1, 0x10000, 0xD0000,
app0, app, ota_0, 0xE0000, 0x120000,
spiffs, data, spiffs, 0x200000,0x200000,
This would allow for 2MB of data for web server files and books in the SPIFFS partition, which felt like enough to be at least useful. Then I used gen_esp32part.py to generate an actual partition table binary blob from the csv file:
$ gen_esp32part.py partitions.csv.new partitions_new.bin
I used xxd to output the important bits in c array format:
rick@nixlap ~/Projects/BannedBookLibrary/idf/library/main$ xxd -i ../partitions_new.bin |head -n17 ✭main
unsigned char ___partitions_new_bin[] = {
0xaa, 0x50, 0x01, 0x02, 0x00, 0x90, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00,
0x6e, 0x76, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x50, 0x01, 0x00,
0x00, 0xe0, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x6f, 0x74, 0x61, 0x64,
0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xaa, 0x50, 0x00, 0x11, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x0d, 0x00, 0x73, 0x61, 0x66, 0x65, 0x62, 0x6f, 0x6f, 0x74,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xaa, 0x50, 0x00, 0x10, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x12, 0x00,
0x61, 0x70, 0x70, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x50, 0x01, 0x82,
0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x73, 0x70, 0x69, 0x66,
0x66, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xeb, 0xeb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xda, 0xa8, 0x74,
0x2c, 0xcd, 0xc5, 0x28, 0xab, 0xd5, 0x0d, 0xf6, 0x41, 0xd3, 0xa7, 0xdd,
I then dropped it in a partition.h file:
unsigned char partition_table[] = {
0xaa, 0x50, 0x01, 0x02, 0x00, 0x90, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00,
0x6e, 0x76, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x50, 0x01, 0x00,
0x00, 0xe0, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x6f, 0x74, 0x61, 0x64,
0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xaa, 0x50, 0x00, 0x11, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x0d, 0x00, 0x73, 0x61, 0x66, 0x65, 0x62, 0x6f, 0x6f, 0x74,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xaa, 0x50, 0x00, 0x10, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x12, 0x00,
0x61, 0x70, 0x70, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x50, 0x01, 0x82,
0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x73, 0x70, 0x69, 0x66,
0x66, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xeb, 0xeb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xda, 0xa8, 0x74,
0x2c, 0xcd, 0xc5, 0x28, 0xab, 0xd5, 0x0d, 0xf6, 0x41, 0xd3, 0xa7, 0xdd
};
unsigned int partition_table_len = 192;
I then wrote a function to overwrite the partition table data with this information. The function first checks to see if the partition table MD5 sum already matches the new table. If so, it's already been flashed and doesn't need to be flashed again. If not, then it updates the partition table.
bool edit_partition_table() {
int result = esp_flash_init(esp_flash_default_chip);
Serial.printf("esp_flash_init result: 0x%x\n", result);
uint8_t current_md5[MD5SUM_SIZE];
memset(current_md5, 0x0, MD5SUM_SIZE);
result = esp_flash_read(esp_flash_default_chip, current_md5, CONFIG_PARTITION_TABLE_OFFSET + OFFSET_TO_PART_MD5SUM, MD5SUM_SIZE);
Serial.printf("esp_flash_read result: 0x%x\n", result);
if (memcmp(partition_new_md5, current_md5, MD5SUM_SIZE) != 0) {
Serial.printf("Patching partition table...\n");
result = esp_flash_erase_region(esp_flash_default_chip, CONFIG_PARTITION_TABLE_OFFSET, 0x1000);
Serial.printf("esp_flash_erase_region result: 0x%x\n", result);
result = esp_flash_write(esp_flash_default_chip, partition_table, CONFIG_PARTITION_TABLE_OFFSET, partition_table_len);
Serial.printf("esp_flash_write result: 0x%x\n", result);
Serial.printf("Erasing NVS partition...\n");
result = esp_flash_erase_region(esp_flash_default_chip, 0x9000, 0x5000);
Serial.printf("esp_flash_erase_region result: 0x%x\n", result);
Serial.printf("Setting default boot partition\n");
const esp_partition_t * part = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_0, "app0");
esp_ota_set_boot_partition(part);
Serial.printf("Restarting...\n");
ESP.restart();
} else {
Serial.printf("Partition table already patched\n");
}
return true;
}
This did not work at first. Whenever I tried reading from or writing to the partition table, the API functions would return a success code but wouldn't actually read or write anything. It took some research and testing but eventually I discovered that the ESP32 framework doesn't allow you to access certain sensitive areas of the flash memory for safety reasons. This includes the bootloader and the partition table. When using Arduino IDE to program an ESP32 the framework is preconfigured for you, which makes things easier in many cases. However, one of the configurations has this safety feature enabled. This meant that I wasn't going to be able to edit the partition table using Arduino.
After some more research I discovered that the official ESP32 framework is called ESP-IDF. It's more complicated to setup and use but offers greater control over the device and the framework itself.
With more trial and error and head banging I eventually got an ESP-IDF environment setup to see if this would fix my problem. To configure the framework, you can use the command idf.py menuconfig. This opens a menuconfig similar to the Linux kernel. You can use this interactive menu to enable or disable features required for your project. This would also come in handy later when I eventually built my custom safeboot firmware and needed to find a way to shrink it.
In the menuconfig is an option for SPI_FLASH_DANGEROUS_WRITE_ALLOWED. This setting must be set to Allowed. You also must set `SPI_FLASH_DANGEROUS_WRITE_ABORTS to disabled. When you exit the menu, it will reconfigure the IDF with this new setting. Then, you can read and write the the partition table!
However there were more hiccups. Working with the IDF directly meant all of the nice Arduino stuff wasn't going to work. I would have to rewrite my firmware code at a lower level. Or would I? I discovered there's a thing called "Arduino as a Component". When this component is added to the IDF, you can use all of Arduino's niceties but also have greater control over the IDF itself! The best of both worlds! I followed the instructions on that page to get the component setup.
I also then ran into some more problems figuring out how to reinstall the various libraries I was using such as ElegantOTA, Async_TCP, ASyncWebServer, etc. I basically needed to clone the repositories into either my projects components directory or into the Arduino component's libraries directory, depending on the package. I also had to tweak some CMakeLists.txt files. I ended up writing a build.sh script included in the repo that handles all of this for me so I don't have to remember or do it manually.
With allllll of this setup, I was finally able to build the project with idf.py build. Once it compiled I needed to flash it over since I didn't have the Arduino IDE to do it for me. I used esptool again.
esptool -p /dev/ttyUSB0 write-flash 0xe0000 build/library.bin
With my firmware image uploaded, things were starting to come together. The main firmware image includes some of the important web server endpoints but not the actual library code. That is stored in a LittleFS partition and must be flashed separately. To do this, I included ElegantOTA.
When you boot the device without having flashed the filesystem it will present you with a setup page.

This page walks you through how to use ElegantOTA to flash the filesystem image and also the safeboot firmware.
I wanted to be sure I could perform OTA updates of the main firmware. Originally I thought I could just use the Tasmota safeboot for this. The problem is that Tasmota safeboot uses your WiFi configuration stored in the nvs partition to do updates. This made me realize that the device saves your WiFi SSID and password in plaintext in the nvs partition. This is not good OPSEC! You wouldn't want to leave one of these light bulbs somewhere with your WiFi credentials just sitting there.
To deal with this, I first have my firmware erase the nvs partition. It also erases the SPIFFS partition for good measure. However without the WiFi configuration data stored in NVS, Tasmota safeboot can't connect to any WiFi network. This means you can't connect back to the device to update the primary firmware. I realized I was going to have to make another custom firmware image for the safeboot partition to deal with this problem.
I found this GitHub repo which contained a fairly minimal firmware example that sets up an access point and hosts a minimal web server to perform OTA updates. It doesn't use Arduino, so it should be less bloated and therefore fit in a smaller partition.
I started from this project and then modified bits of it here and there as appropriate for my project. I ended up having to disable some other things in the menuconfig to get the image to fit in the safeboot partition, but it did work!
The safeboot partition is not password-protected, but the admin features of the library are password protected, and you use those features to reboot into safeboot. Good enough.
The main experience begins with the index page. This page features an image of a yellow shipping container with a door.

I chose it as a reference to the Ben Brown Library short story mentioned earlier. The image does take up valuable storage space, but I like it so I left it. I included an image "glitch" effect on this page as to make it feel more "hackery". I thought it was cool. I had no idea how to make something like this though. I found the code for it, annoyingly, in a YouTube video. I modified it slightly to suit my needs.
The main library pages were all hand coded by me the old fashioned way... searching for the stuff I wanted to do and then copying, pasting, and modifying to make my own thing. Or else reading about how to do something similar to what I wanted to do and then banging my head against a wall untl I figured it out. I'm not a fan of generative AI. I had to learn more about CSS to make it look pretty. My original plan was to have it be a really basic HTML index page. Just a listing of files. But I eventually decided I wanted it to look cool and be more fun. It also allowed me to learn CSS which I found pretty interesting, actually.
The main site is fairly simple.

There's a subsection to describe what you are looking at, a section for the books including title, author, and reason it was challenged or banned. Then there is a references section for external links. Though the links won't work while the user is connected because the Banned Book Library's access point doesn't have an Internet connection.
There is also an administrative panel at /admin.

This page is password-protected. It allows you to control the LED color temperature. The idea is that if you drop this somewhere in public, you can try to match whatever color was there before so it is less noticeable that anything changed. This was actually really easy to implement. The company that sells these light bulbs tells you what GPIO pins are used to control each LED color. So you can use AnalogWrite() to set the intensity of each color.
// Turn on cool white light
void handle_cw_on() {
// Turn everything off
handle_off();
// Enable cool white
analogWrite(CW, BRIGHTNESS);
prefs.putUChar("color", COOL);
}
I have the library set up to store the color setting in NVS so the next time the bulb is turned on it will return to the same color.
The admin functions also has a button to link to the restore page.
Restore allows you to somewhat restore the partition table and boot into safeboot.

This restore function actually reboots into the custom safeboot partition where you can flash another firmware over top of the Banned Book Library firmware. This could be a new version, or it could be Tasmota, ESPHome, or whatever.
The one problem with restore is I haven't figured out a good way to restore the safeboot partition yet. This means if you do flash Tasmota back, when you click "Firmware Upgrade" in the Tasmota interface it will reboot back into this custom safeboot. It should be usable to update Tasmota but it will not play nice with Tasmota's upgrade experience.
Because of this quirk, the partition restore doesn't completely restore the partition table. It actually leaves safeboot's sub type set to OTA_1 instead of restoring it to Factory. I think Factory partitions are not able to be updated OTA. I left it set to OTA_1 in the hopes that it may somehow allow for an update via a means I haven't figured out yet.
Having the web server running is well and good, but how would a user know how to reach it? A user who discovers this organically is likely to connect to the open access point and then realize there's no internet connection and give up.
Many access points have captive portal technology. When you connect to the access point your device prompts you to authenticate to the network. It directs you to some web page where you can log in. This is common in places like hotels. I thought I could implement something like this for the banned book library.
I didn't know exactly how this worked, so I looked into it a bit. It seems the old way of doing it was to essentially intercept whatever HTTP request the client made and respond with a redirection to the captive portal. This doesn't work so well nowadays because just about every web site uses HTTPS. The WiFi controller wouldn't be able to intercept and modify the traffic unless the client was configured to trust certificates signed by the controller.
These days it seems there are a few methods. One is to use a DHCP option to specify that a captive portal is in use. Another is that various device types will make specific requests to test for the presence of a captive portal.
As usual, I went looking for example code that already existed to start from. I managed to find some code on GitHub that implements captive portal for the ESP32. It does two things. First it sets up a DNS server that responds to every request with its own IP address. That way no matter what URL is requested, it will direct the user to the EPS32 web server, which is the Banned Book Library. Second, it catches some specific HTTP requests and sends specific redirect responses. It seems the various requests are for Microsoft devices, Android devices, iOS devices, etc. I used a chunk of this code in my own project, which sped things up quite a bit.
// Captive portal things
// https://github.com/LuanTechAutomation/esp-captive-portal/blob/main/src/main.cpp
void config_captive_portal() {
server.on("/connecttest.txt", [](AsyncWebServerRequest *request) { request->redirect("http://logout.net"); }); // windows 11 captive portal workaround
server.on("/wpad.dat", [](AsyncWebServerRequest *request) { request->send(404); }); // Honestly don't understand what this is but a 404 stops win 10 keep calling this repeatedly and panicking the esp32 :)
server.on("/generate_204", [](AsyncWebServerRequest *request) { request->redirect(localIPURL); }); // android captive portal redirect
server.on("/redirect", [](AsyncWebServerRequest *request) { request->redirect(localIPURL); }); // microsoft redirect
server.on("/hotspot-detect.html", [](AsyncWebServerRequest *request) { request->redirect(localIPURL); }); // apple call home
server.on("/canonical.html", [](AsyncWebServerRequest *request) { request->redirect(localIPURL); }); // firefox captive portal call home
server.on("/success.txt", [](AsyncWebServerRequest *request) { request->send(200); }); // firefox captive portal call home
server.on("/ncsi.txt", [](AsyncWebServerRequest *request) { request->redirect(localIPURL); }); // windows call home
// catch all
server.onNotFound([](AsyncWebServerRequest *request) {
request->redirect(localIPURL);
Serial.print("onnotfound ");
Serial.print(request->host()); // This gives some insight into whatever was being requested on the serial monitor
Serial.print(" ");
Serial.print(request->url());
Serial.print(" sent redirect to " + localIPURL + "\n");
});
}
The device is limited to 4MB of total storage. This is not much space. The few epub books I looked at were around 350KB each. So the light bulb can host a few of these. I had originally imagined a web server with tons of banned books available.
At first this was disappointing but now I actually have grown to like the idea that you are limited on what books you can choose. This means that each dead drop will be representative of the person who created it. They have to pick and choose specific books that are important to them or that they feel are important for others to have access too. I like that idea.
I also like the idea that there could be multiple of these around a given municipality and each one would have different things to find. It could make it more fun to go explore and find these things and see what was left on them for you to discover.
One thing I'd like to do is add sliders to control light color. This would allow for all of the RGB colors as well as finer tuned control over the white color temperature. This way you can more closely match the lights already installed in a given location.
I was talking with a friend about this idea and the storage limitation and he thought it would be cool to have these devices form a mesh network. They could use something like distributed hash tables to make all of the books in the mesh available to someone who connects to any device, just as long as the devices are in range of one another. I thought that was a fun idea and may be worth exploring.
Aside from all of that, I have several other fun ideas for repurposing smart devices. These ESP32 chips are so cool! They are dirt cheap and very capable. I will very likely have more ESP32 projects in the future now that I know how to get up and running with them.