Chapter 1 - HTML
We start the Practical Workshop series with a chapter on HTML, which although a broad subject, needs no special hardware and aims to impress.
No assumptions are made about prior knowledge or experience, but you are expected to have already installed Annex-Wifi Basic.
The working principle of the Basic interpreter is to parse through the Annex-Wifi Basic script and execute its instructions.
If the script contains any ‘webby’ output it is ‘served up’ to make it available for viewing in any browser on any device that can connect to it.
Web output is viewed in the Output page (opened by default when connecting), but script development and testing is done from the Editor page.
The default HTTP port is 80, but can be changed in the Config page if wished.
Only 1 open port is required because websocket connections share the same HTTP port.
BrowsersWeb pages can be a thorny issue because different browsers interpret some of the
html and
css instructions differently (
or ignore them completely), causing the same web page to appear differently in different browsers.
Browse to
https://www.w3schools.com/css/css3_gradients.asp
Scroll down a bit until you see Browser Support section. Don’t worry about it, but take note that all those browsers interpret ‘Gradients‘ a little differently (the Annex-Wifi Basic meter$ web component uses a green gradient).
They all nearly recognise a common language sufficient to fool a casual glance of most things, but big companies and big heads have big ego’s, so they each have their own eccentricities which they try to impose on ‘the’ world.
I choose to use Firefox because it is the only remaining independent mainstream browser which doesn’t have a parent corporate axe to grind… so I feel I should be able to ‘trust’ it more than corporate honey-traps with vested interests and ‘beyond-the-law’ arrogance. That’s just my personal opinion, but which I need to point out because everything I present here has been developed using Firefox… (Firefox is free, so use it if needed).
Scroll down the w3schools page a bit more to see the Example code under a red and yellow gradient graphic, and note how many hoops need to be jumped through in order to make that single gradient effect display correctly in all the different browsers.
That may be justifyable for a big website hosted on big servers catering to a big audience, but perhaps not so essential for my personal use on my tiny 1Mb devices with limited resources.
Gradient was just one example, rounded corners is another, and there are many others (see the big list here)…
https://www.w3schools.com/cssref/css3_browsersupport.asp It is seldom essential to incorporate such bells and whistles other than to impress oneself or others, so bear in mind that high ideals and low resources can cause a conflict of interest which may require some compromise between style and functionality.
First StepsBy default, the device configures itself as an AP (Access Point) at IP address 192.168.4.1 with a wifi SSID called ESP(plus MAC address).
Connect your computer wifi to the ESP’s SSID, then open a new browser tab and enter the IP address into the address bar.
A default ‘Output‘ page will open, right-click on the Editor button and open the Editor page in a new tab.
TIP: you can shuffle the horse in front of the cart by dragging the Output tab to the right of the newly-opened Editor tab (or vice versa).
If your browser is not maximised, you can drag the Output tab completely off the browser window, then adjust the 2 separated windows to be both visible side by side. This would allow you to edit the script from the Editor page while viewing the results in the Output page at the same time, without needing to keep switching between tabs.
TIP: having created yourself a convenient remote browser ‘console’, you can now tweak it to suit your preferences if you wish.
In the Editor page, hover over each of the icons just above the edit window to see some handy tools sitting in plain sight.
Notice the dropdown font-size box that lets you change the size of your text in the editor. But before you do, you may wish to adjust your overall browser screen size using CTRL+ and CTRL– (incidentall, CTRL+ and CTRL- also controls the font size in Notepad ++ for those who use it).
Coloured syntax highlighting is great for making the ingredients shine out from the stew. But I find the dim grey flashing cursor is very difficult to locate under the blue haze of the currently selected line – especially on a long line with no clue to its whereabouts – so I often click the tool icon (paintbrush?) to the left of font size to “toggle syntax highlight on/off“.
Script Feedback
Annex-Wifi Basic error messages are sent to the serial port AND to the wlog (stands for Weblog) window of the browser editor, which means it is possible to monitor errors either with a hardwired serial port connection OR with a wifi connection, but you obviously need one or the other.
User output goes to wherever the user sends it, which can be Serial or Serial2, or UDP network message, or wlog, or browser web page… this chapter is about html so we will focus on sending output to a web page.
InputUser input can be obtained from serial monitor, UDP network message, web browser, the script, or ‘Immediate’ window of the Editor page.
Each type of input is read differently, serial and UDP are read using program code, while a script must be RUN, but the ‘Immediate’ facility allows typing instructions directly into it’s window then pressing Enter to read and process them.
This offers 2 forms of console for interacting with an ESP device depending on whether the device is serial hard-wired or wifi network connected.
- For local console use a hardwired serial port monitor (via your USB) for input and output of non-networked devices.
- For a remote console use the Editor page Immediate window for keyboard input and the wlog window for output.
Web Console
Each type of output is addressed differently even though the message content may be the same.
If you type “Hello” into the Immediate window then press enter, the wlog window above it will show ‘Syntax error‘ because it is not a valid instruction that Annex-Wifi Basic can recognise.
The message content needs to be ‘steered‘ somewhere recognisable – so type PRINT “Hello” in Immediate and press Enter (which in future will be abbreviated to: Immediately enter ‘PRINT “Hello”‘).
Notice that wlog shows no new error message, and the serial monitor (assuming it is connected) shows your message.
Back in the Immediate window, click the cursor at the end of your PRINT “Hello” entry, then press Enter, then repeat it again.
Notice that your message has been repeated on new lines in the serial monitor.
Back in the Immediate window, Immediately enter wlog “Hello”, and notice that this time the message has been steered to the wlog.
The wlog is the browser web log where all browser output code is logged to, whereas the Output page is where the browser output code is actually displayed after being interpreted by the browser.
If you do not yet have a separate Output browser tab or window open, right-click the Output window button and open in a new tab.
Click the ‘Clear’ button just under the wlog window to avoid any confusion from clutter, then Immediately enter HTML “Hello”
Check that your message was displayed in the browser Output window, then check the wlog entry which shows what code was sent to the browser.
(as things become more familiar you can use the wlog entries to debug why your html is not displaying as intended)
Use the Immediate history again by clicking the cursor at the end of the last entry then press enter, noting the ability to move back through previous Immediate sent message history if wished (
so re-typing same messages as previous is not necessary).
Look at how the last message was displayed in the Output window, and notice how subsequent html messages get ‘concatenated‘ to previous messages, unlike serial and wlog messages which by default append a carriage-return/linefeed after them which causes subsequent messages to print to a new line.
This is because unlike most other languages, HTML (HyperText Markup Language) is purely text-based, and not intended for any particular types of output device, so it has no notion of typewriter carriages or keyboard Enter keys – therefore does not use nor recognise any non-printable special characters such as ASC(13) carriage-return and ASC(10) line-feed.
All the diversity of life is coded in gene sequences comprised of just 4 different letters (bases), and those genes do not exhibit their coded traits, they only form the instructions for producing those traits.
Similarly, the output diversity of HTML is achieved using just the standard printable text characters coded into string sequences.
Therefore HTML is not ‘WizzyWig‘ (What You See Is What You Get), it is an abstract stream of text instructions useful for any type of html output device to interpret and ‘render’ into an appropriate output format, irrespective of display size.
Why so complicated? Actually, it’s not so complicated as it appears, once you know how it works…
Think of a travelling magician touring the country putting on shows in tents which are supplied and erected by the local town prior to his arrival.
He won’t know in advance the exact size and shape of the tents, and doesn’t really care, so long as they meet his specifications of how many seats are required, what size of demonstration area is needed, how much distance between the display table and the audience, what access is needed to the display area, and the minimum space around the display area.
Given that information, the local town will erect an appropriate sized tent, set up the demonstration area, then add as many rows of chairs as are needed to fit in the required number of seats.
That is basically how html works, using a system of matching ‘
start’ and ‘
end‘ tags to enclose the specified layout and content details…
<head>
<title>Magic Tent</title>
<style>
colour: red carpet; side aisle borders: 2; display size: 8 wide 4 high; display padding: 1
</style>
</head>
<body>
<p> (The ‘p’ denotes a ‘paragraph’ of text) This content represents the required chairs. It does not matter how wide the tent is, because when a row is filled, the content will continue to form the new row, until all the contents are accomodated. A narrow tent will have more rows of fewer tents, and vice versa… which is the same sort of effect that happens to this content if you resize the window… try it to see the effect for yourself.
</p>
</body>
So although the html system looks cumbersome and non-intuitive, it is not actually difficult. It is only daunting because of the large number of different commands available with their appropriate specific syntax… but that is the same when faced with ANY new language (and few others have such a wealth of help material available for them like html does).
What makes html so special is that those few simple plain text characters can provide such huge diversity and capability by the simple means of having some open-ended ‘expansion’ tags which allows almost anything else to be included.
The <style> </style> tags can specify the styling of any and all content. Small amounts of styling can be included inline with the content, alternatively all styling can be neatly declared in the <head> area, or can be saved into a separate CSS stylesheet.
A matched pair of <script> </script> tags can enclose complete working code snippets from other languages – famously javascript, but also others – thereby offering open-ended power to html. Javascript can alternatively be loaded from an external file.
But don’t worry about all of that … you don’t need to become a web developer or javascript programmer to achieve your desired html results, you just need to know a few key tips about how to make things work for you.
And that’s what this chapter is all about, helping YOU display YOUR Annex-Wifi Basic web content HOW you want and WHERE you want.
First though, let’s demonstrate how Annex-Wifi Basic blurs the boundaries between its built-in web features and internet web content, by using its ‘incidental‘ capability to act as a web server for ‘serving’ raw web content – which is a triumph in it’s own right.
Chapter 2 - Hard Time
When timekeeping is critical to a project, eg: for scheduled control, then relying on ESP time may not be enough.
I have experienced many thunder-storms which have caused power outages and device reboots.
If coupled with loss of internet connection (as is often the case) it will cause the ESP to start up on “groundhog day” (1/1/1970)
Or maybe you just prefer to stay isolated from external internet access, or perhaps need to be mobile in a vehicle without an internet connection.
An effective way to keep accurate time during power outages and without internet access to time servers, is to use a hardware Real Time Clock.
Battery-backup RTC modules are available for less than a quid and can keep accurate time even when your ESP loses power. The ESP can read the correct RTC time again after it restarts. They use i2c, therefore you are able to use the same 2 pins for additional i2c modules if you wish.
(there is even a Wemos D1 Mini RTC plug-in shield available – it doesn’t get much easier than that).
A small button-cell battery should last for many power-outages, but rechargeable button-cells are available which might last indefinitely.
Chapter 3 - Temperature
Annex-Wifi Basic currently caters for Dallas 1-wire and DHT temperature sensors (but others may be added).
Using Dallas and DHT is eazy-peazy, and the following snippet shows how a DHT11 on pin 13 and multiple Dallas 1-wire sensors on pin 14 can all be used at the same time…
print tempr$(14)
dht.setup 13, 11: print dht.temp
But using them efficiently takes a little bit of thought.
NOTE: heat is generated by the movement of electrons, therefore
every electronic device generates heat to some extent.
So if you wish to monitor
ambient temperature you must ensure
open-air access to avoid the sensor being affected by the
build-up of internally-generated heat – because an enclosure with vents is still an enclosure which could retain a different temperature inside compared to outside.
Conversely, if you wish to monitor heat
generated by a device then the sensor needs to be in good thermal
contact with that device – possibly helped with the use of thermally conductive grease to reduce insulating air gaps.
In all cases, it requires hardware and software to interface with the sensor output to convert it into a usable format suitable for sending to some sort of display device – this chapter will focus on using the browser to display the temperature reading.
The system cannot be devoted to only sensing temperature, nor to only displaying a previous reading, because it must periodically do both.
Therefore an important system requirement is a timer to periodically keep reading the sensor output and displaying that reading.
The question then is… how frequently ?
That frequency will depend on what else is going on… if it is just being used as a thermometer then not a lot.
If it is used as a thermostat then it has to do comparisons with threshold temperature settings, control gpio’s, and also to divert focus towards changing the threshold temperature settings (turning the thermostat up or down) whenever wished.
If being used as a timed temperature controller then it will need to maintain accurate time / date facilities as well as compare against a list of different temperature settings at different times / dates etc, plus the ability for user-adjustment of all settings – and ensure proper gpio control of relays and display outputs etc… quite a lot to do.
Annex-Wifi Basic has 2 timers available for use, but even just handling 1 timer properly needs some careful thought.
Timers work by interrupting whatever else the system is doing in order to service that timer interrupt before returning control back to the system to carry on with whatever it was doing before being interrupted.
Consider the situation where the previous interrupt has not yet had sufficient time to finish servicing that interrupt completely before another interrupt comes along to interrupt completion of the first. And that is just 1 timer that cannot keep pace with what it is required to do… so you can imagine the chaos of interrupting a busy timer with a second interrupting timer.
And that is without even considering any of the other event-driven interrupting facilities that might be in use.
Common-sense and compromise is required.
There is no point having a timer banging away every second if you are not displaying the seconds,
If you really need to count the seconds, it would be pointlessly impractical to also try to keep sampling the temperature at such a rapid rate,
TIP: if you have different tasks to do at different frequencies, set the timer to handle the fastest, and have it keep decrementing one or more pres-set countdown variable until they eventually reach zero, whereupon they can process their less frequent tasks then pre-set their countdown variables ready to start another countdown delay again.
As an example – the timer could update a clock display every second, while decrementing a readtemp countdown variable from it’s starting value of 30, which could be used to keep sampling and displaying the temperature at 30 second intervals.
The same principle can be used to carry out multiple different tasks at different rates, but perhaps take a leaf out of natures circadian rhythms and use prime numbers as countdown periods to reduce coincidences of them all happening at the same time.
CALIBRATION: If accuracy is important you could use the 2 extremes of freezing and boiling points of water at your altitude as reference points.
Calibration is easier if you have a non-contact temperature gun available, which can be bought from places like ebay for less than a tenner:
I suggest one with a specified “Distance Spot Ratio” of 12:1 which will allow taking more pinpoint distance readings of hot-spot objects within the target area, ie: a specific cable that is getting hot, or an overheating transistor, or a circuit-breaker with high-resistance contacts, etc.
A non-contact gun makes it easy to check calibration of your sensor readings, which then offers opportunity to add an appropriate calibration offset bias to adjust results if required.
Chapter 4 - Relay Control
Hobby relay contacts are typically rated at 10A 220V = 2,200W
Contact rating is important because current flow can have the kinetic energy of a battering ram – so the more current flowing through contacts when they start to open, the more electrical energy will spark across the opening gap, taking metal from one side and depositing it on the other to effectively re-shape the contact surface area. The rate that this happens will depend on the quality of the metal contacts and the amount of current they have to handle, Continued use will transfer sufficient metal to leave pitting on one side and create carbonised build-up on the other, reducing the contact surface area and increasing contact resistance. Resistance to current causes heat, so eventually the deteriorating contacts can heat up enough to melt surrounding insulation, or even act like an arc welder to fuse the contacts together so that the electricity and current flow cannot be stopped. Such a situation is very dangerous and potentially lethal… and this is not just scare-mongering, it is from painful personal experience.
Electricity causes muscles to involuntarily contract – the heart is a muscle, and it only takes about 35mA to make the heart contract sufficient to prevent it from beating… which is why Earth Leakage Circuit Breakers are rated to trip out at 30mA leakage.
If your hand touches Live Mains causing current to flow through you to earth, the electrified muscles can tightly spasm into a vice-like grip preventing you from letting go again… similarly the heart will contract into tight spasm and stop beating, then it doesn’t take long for oxygen starvation to cause blackout and eventual death. A 30mA ELCB won’t prevent a shock, but it will shut off the current before it has time to kill.
Birds perch on live wires without harm because no current discharges through their body, due to all points of contact being at the same potential.
You could also touch a live wire without harm if you did not provide a path for the electricity to discharge through… such as your feet in contact with the ground, or through one hand and arm across your chest to the other. That’s why I never use both hands when working near Live electricity which has potential to kill me, and I never wear footwear with any metal, and preferably I place a rubber mat to stand on, and never lower the resistance of my body by having a cuppa just beforehand… all simple precautions that reduce risk and increases chance of surviving a mishap.
The lack of controls of many eastern manufacturers and sales chains allows them to pretty much do and say what they want without fear of consequences or compensation claims from hapless victims the other side of the world.
So don’t assume the ratings and specs you read are true or tried and tested, or even relevant for the purchased item.
Don’t push your luck by using appliances that are rated close to the stated contact ratings, because even if able to cope now, eventually it will fail.
Equipment includes the added complication and expense of being earthed for safety reasons unless it is deemed safely ‘double insulated’.
Many eastern-made Mains relay modules are not earthed, and are barely and inadequately insulated.
So add a pass-through earth wire yourself between the Mains input plug and the switched Mains output socket to provide an earth for appliances.
The reality is that the only person in the world with any vested interest in the safety and well-being of you and the things you care about is YOU.
Therefore the onus is on YOU to ensure that your project is not a danger to you or others, including pets and property.
There are now several eastern manufacturers producing cheaply available ESP Mains-switching relay modules, which often means it is more convenient and cost-effective to buy something ready-made than to construct your own heath-robinson home-brew.
I’ve tried many different types, but Itead/Sonoff are usually the cheapest and they usually do work.
The original Sonoff TH is cheap enough at a fiver and gives gpio14 to play with – which is what I’ve used for the Differential Thermostat project.
There are now TH10 and TH16 versions available which bring gpio14 out to a side-mounted 2.5mm 4 pole jack socket to allow a range of pre-wired one-wire temperature sensors to be plugged in. A step in the right direction, but could have been better, and still leaves room for improvement.
What all these ESP-based switched relay modules have in common is the ability to connect a UART (even if un-populated holes) for re-flashing.
One of the big advantages of using Annex-Wifi Basic is that you only need a serial connection to flash the firmware for the first time, then all programming and control is done over wifi, and subsequent firmware updates can be OTA.
Chapter 5 - EasyNet
I have been developing EasyNet to let me create a smart interactive system which I can keep on evolving indefinitely to suit my future needs.
There is no need to know IP addresses, no network configuration to bother with, and no rigid framework structure to adhere to.
The same basic network script is used on all nodes, making interactive networking simple for my own needs, and for anyone else who wants to use it.
Each EasyNet node is given a unique name of choice (or one will be created automatically) allowing nodes to address each other by their unique names.
Nodes communicate by broadcasting simple UDP text messages which consists of the target name plus whatever info or message is being sent.
The user decides what info is sent, by deciding what specific key words (instructions) the different nodes should respond to, and how they should respond.
Optional data may also be included if wished.
Target_name Instruction [ optional data strings ]
As well as responding to their unique name (and also their IP address if needed), nodes can also be assigned and respond to multiple ‘group’ names, and “ALL”,
I don’t claim perfection, and undoubtedly it could be optimised and improved, but it works well for my purposes.
It is not intended to be super-fast or highly efficient, it is intended to be super-easy to use, and highly flexible and expandable.
I’ve already published some projects which allow EasyNet nodes to interact with each other to create automation and control systems.
Sensor alerts and system announcements can be spoken from one or more Voice Announcer TTS nodes by sending appropriate text msgs to be spoken.
An
Alerts Monitor node can collate sensor alerts from various remote nodes to visually display sensor channels on a central neo-pixel monitor.
IR receiver nodes can read InfraRed remote control signals from different locations and distribute IR signals to one or more remote IR emitter locations.
IR signals can either be emitted to control IR equipment, or be decoded by the esp node to control any of its local functionality, such as operate relays etc.
Thus existing EasyNet projects already allow triggered sensor alerts to control other devices, and/or send IR signals to control equipment, and/or send TTS messages to speak relevant announcements, and/or display visual sensor channel status, all at various selected locations… and it is easy to add more functionality.
For example: to suit my own needs I have created a weird “Sentry” to act as my central system controller.
It reads external digital sensors using analog inputs to provide adjustable 0 to 5v logic level thresholds (for improved trigger reliability down long lengths of thin wires)
Announces Alerts via distributed audio Text to Speech (can be used to announce some types of events at some locations and other types of events at others in different voices)
Transmits Alerts to any nodes who may be interested (sending msgs to EasyNet ‘groupnames’ makes it possible for multiple listening devices to each respond)
Permits other EasyNet nodes to request and receive system status information, allowing multiple remote ‘System Console’ devices to monitor and control at the same time
Uses Wemos D1 Mini footprint to allow the same script to work with either an esp8266 or esp32 device plugged into the wemos socket (by using software flags)
When using an esp32, includes RJ45 cable ethernet capability for bridging EasyNet traffic between different wifi networks, plus adds SD card for file logging
Provides visual central system clock, plus optional scheduled speaking clock (muted outside of socially acceptable hours), automatic announcement volume adjustments
EasyNet allows any of the local “Sentry” features to be made readily available as remote network functionality for any other EasyNet nodes to use..
So any remote nodes could write entries to the Sentry central Log file, or send the Sentry messages for announcement, or sync to its RTC date and time, etc.
Shared functionality is accessed simply by sending a message to the required target name with an appropriate instruction for it to recognise and respond to.
Conversely, any messages NOT intended for a node can either just be ignored, OR automatically relayed elsewhere if it has been configured to be a bridging node.
So messages not intended for nodes on a local wifi network could be automatically bridged via cable ethernet to the nodes on a remote wifi network and vice versa.
Or a serial cable bridge could relay messages to another Annex serial bridge… or even to a totally different serial platform such as arduino or ras pi etc – .
The Sentry was planned as a dual standard device which I might perhaps initially publish as an esp8266 project, while continuing development of a pin-compatible plug-in esp32 version to take advantage of the additional Annex32 features such as cable ethernet and SD card, while using exactly the same script, simply changing software flags depending on what hardware was being used. That all went as planned, and It’s been working reliably through power cuts and lightning storms for months without problems or false triggers.
But priorities change – and it is probably too weird to be worth publishing as a project anyway – so I’ve mentioned it here just to illustrate how EasyNet has let me cater to my own needs .
An optional “Watchdog” monitor (Sonoff S20) was later added to supply power to the Sentry device. If the watchdog is present and enabled, it periodically sends a request for acknowledgement to the specified ‘Sentry’ watch client. If the watched client fails to respond within the specified ‘timeout’ period the watchdog records the failure in its log then disables subsequent client monitoring (to prevent endlessly transmitting to a failed client) before cycling power to the client off and back on again to try to reboot it.
The “Watchdog” node recognises a local “WATCH” EasyNet instruction, thereby allowing the watchdog facility to be remotely turned on or off.
Watchdog_target_name Watch ( 1 / 0 ) [ watch_period ] [ watch-client_name ]
where 1 or 0 turns the watchdog monitoring feature on or off, and optional [watch_period] and [watch_client_name] allow those parameters to be changed if required.
If cycling power to the unresponsive Sentry causes it to successfully reboot, it sends “WATCHDOG WATCH 1” at startup which causes the Watchdog (if present) to re-enable monitoring of the watch client (Sentry) again.
If there is no Watchdog monitor present then that bootup message will just fall on deaf ears of course, but if a Watchdog is available its log will contain a record of all the Sentry failures, even though the Sentry may have been successfully restarted every time it failed.
Behind the Scenes
By default, UDP broadcasts do not include any error-correction or hand-shaking facilities – they are just ‘fire and forget’ – which may be ok for a few nodes on an isolated wifi subnet, but cannot be depended upon in a busy shared network with lots of noisy traffic.
So EasyNet offers optional hand-shaking capability to make udp communications more robust if needed.
Messages are usually sent as a response to some sort of trigger event, so the response will have been hard-coded by the user into the appropriate subroutine code.
Therefore it is up to the user to decide whether the message is important enough to require ACKnowledgement, or can just be ‘fire and forget’.
This is achieved by using an appropriate user-sub…
SEND msg$ is just a fire and forget udp broadcast
SENDQ msg$ adds the msg$ into a RETRY Queue for periodic resending until either it is ACKnowledged or a pre-defined Time2Live expires.
SENDQ adds ID=(unixdate+unixtime+Time2Live) to the end of the message, which the sending node uses to time out unACKed msgs from the queue.
The target responds to any received messages containing ‘ID=’ by echoing back an ACKnowledgement of the received msg to the sender (is all done automatically).
The user can set the default value of Time2Live (in seconds) for whatever duration the sender should keep re-trying to send the messages.
Bear in mind that if a node becomes unreachable then no sent messages will be acknowledged by it, so the senders SENTQ will just keep on growing bigger until the un-acked messages eventually expire and are deleted from the queue… something to consider when changing Time2Live.
The periodic retry timer only tries to resend the first (next) unexpired message still in the FIFO retry queue, then adds it back on to the end of the retry queue… so any remaining un-acknowledged msgs are being continuously cycled from back of the queue to the front from where they are periodically tried to be resent.
If a node is kept busy doing other tasks, increase it’s periodic timer1 frequency to something it can more comfortably manage (eg: 5 seconds).
Messages could be prioritised for importance by attaching different Time2Live durations for different message urgencies if needed.
Chapter 6 - Serial
NOTE: Serial error reporting is sent to the hardware serial port for viewing with the serial port monitor – errors messages are not sent to serial2.
The ESP-8266 devices use
GPIO1 for
TX and
GPIO3 for
RX –
Chapter 4 – TX RX Hack shows how to make alternative use of those gpio’s.
The hardware serial and software serial2 ports both have similar usage instructions, as demonstrated in the Dual Serial Portal Bridge project.
Chapter 7 - GPIO Expander
There are only a limited number of ESP GPIO pins available to use, particularly on devices like ESP-01 and Sonoffs modules.
One of the excellent features of
>Basic is its ability for ‘non-contact’ OTA updates even on 1Mb devices.
This makes it practical to utilise TX and RX as gpio01 and gpio03, as demonstrated in this:
TX RX Hack But 2 extra GPIOs won’t go very far, unless they are utilised for connecting i2c multi-IO Port expanders.
One PCF8574 multi-IO module offers 8 additional ports, which can be configured as Inputs or Outputs.
More modules can be added to the i2c bus to increase the available gpio’s in banks of 8, ie: 8, 16, 24, 32.
Other platforms typically continuously loop to check for change of port input status, so are always ‘busy’.
Because >Basic is ‘event-driven’ it can take advantage of interrupts to snooze in between pin changes.
This means it can monitor many inputs while doing other tasks and still respond immediately to triggers.
Some like these blue ones are designed to be physically connected into each other.
User-selectable 3-bit addressing theoretically allows addressing of up to 8 devices.
Port direction is software defined, so these could be 16 inputs or 16 outputs or 8 each.
And of course other types of i2c modules might also be daisy-chained on the i2c bus.
Chapter 8 - ADC Expander
The PCF8591 ADC / DAC converter modules shown here are available for about a quid. They comprise:
a. 8-bit DAC (digital to analog converter) b. four 8-bit ADC (analog to digital converters). c. on-board thermistor, LDR (light dependent resistor), and POT (potentiometer). These last 3 items are for analog experimentation, and can be disconnected by removing jumpers. So note that the 3 jumper links are not for addressing, which is fixed at 72 (decimal). NOTE: despite any apparent similarity of boards, the left-hand (i2c) connectors are very different. The VCC’s and GND‘s are on opposite sides, and even the SDA and SCL pins are transposed.
TIP: the bottom module has the correct pinout to plug directly into the blue PCF8574 modules. Thus offering 4 x ADC + 1 DAC + 8 GPIO‘s from 2 modules plugged into the 2-wire i2c bus.
NOTE: the reason for choosing to use an i2c device is because it only requires 2 gpio pins to be available, so therefore can even be used with Sonoffs etc by utilising their TX and RX gpios.
Using these modules is quite easy once you are aware of how they are accessed on the i2c bus. DAC Writing data to the DAC is just a matter of opening a channel to that device address, send an instruction to explain the transaction requirements, then write the expected data, and close the channel again afterwards.
Each i2c device responds to a specific address on the i2c bus, which is hardwired to 72 (decimal) on these PCF8591 modules. So let us first define an easy constant to address them with… PCF8591_address = 72 To send or receive any data to any i2c address requires waking up that address using…
i2c.begin PCF8591_address Now send an instruction to that waiting address… i2c.write 64 ‘this tells the PCF9591 to use the next incoming byte of data to set the value of the DAC Now send the expected byte of data to set the DAC value (V min = 00, V mid = 127, V max = 255)… i2c.write V After transaction is completed, close the connection to that address. i2c.end Reading data is similar – open a channel to the address, write an instruction to request the required read transaction, notify how many bytes to read (which can vary according to requirements), then read them – this process is demonstrated in the upcoming analog joystick project..
For now, let’s complete the PCF8591 DAC overview using a simple script to send an incrementing number to the DAC then similarly send a decrementing number, and repeat it 5 times so we can see something happening.
There should be an LED connected to the DAC output, which will brighten and dim as the DAC output voltage rises and falls. This is handy for demonstration purposes, but be aware that the LED will ‘load’ the DAC output and cause inaccurate results (cured by a snip).
Basic:
‘ PCF8591 DAC ‘i2c.setup 4, 5 ‘i2c RX and TX pins need to be configured as appropriate i2c.setup 5, 4 ‘i2c RX and TX pins need to be configured as appropriate PCF8591_address = 72 ‘set to module i2c address for a = 1 to 5 wlog str$(a) for v = 0 to 255 i2c.begin PCF8591_address i2c.write 64 i2c.write v i2c.end next v for v = 0 to 255 i2c.begin PCF8591_address i2c.write 64 i2c.write 255 – v i2c.end next v next a wlog “finished” ‘———– End ———-
|
Chapter 9 - Rotary Encoder
These rotary encoders are available for about 1 pound for either the panel mount version or the breakout version (buzz out the misleading labels).
They look similar to a ‘volume control’, but they function in a completely different way, able to continuously rotate without end stops, and giving 2 out-of-phase streams of pulses which can be interpreted for rotational direction, speed, and position.
If the ‘common’ (centre pin) is taken to ground, and the 2 encoder outputs taken to pulled-up gpio inputs, they will generate pulses when the shaft is rotated (think engine camshaft).
The internal ‘camshaft lobes‘ are arranged so that when one changes state the other will be On when rotated in one direction but will be Off when rotated in the other direction.
So it is possible to monitor one pin for change of state, then branch on interrupt to compare its level with the other pin, which would indicate the direction of rotation.
Each pulse, in any direction, causes an interrupt – so knowing the direction allows controlling something one step in the appropriate direction.
Mine are 20-step devices, so a full rotation generates 20 pulse steps, therefore dividing the pulses in any direction by 20 will give revolutions.
Some commonly have 12 steps. Rotational speed can be worked out from how many pulses are received during the period of rotation.
Typically, another pair of pins on the rotary encoder offer a momentary push button switch which is operated by pressing down on the shaft.
So if the two functions are combined it offers a way to move up or down through a list then press to select a chosen item – ie: navigate a menu.
NOTE:
If the 2 outputs are ‘polled’ for position change in a loop, some pulses could be missed, but as rotary position is relative it probably won’t matter.
If using ‘interrupts’ to catch every pulse, the interrupt code must be fast enough to complete execution before being interrupted by the next pulse.
So better to carry out time-consuming operations, such as writing to the browser, outside of the interrupt routine, perhaps by using a timer routine.
Chapter 10 - Menu's
A menu allows navigating through available options to select a preferred choice.
For simplicity, these examples will use an Annex listbox$ as a quick and easy means of navigation and selection.
Listbox$ takes a comma-separated list of options and returns the selected choice in a previously declared listbox ‘variable’ (which we will call choice$).
And quite conveniently for our purposes, its comma separated list of options can also be supplied in the form of a text variable rather than being embedded.
When a selection is made from a listbox$ it triggers an OnHtmlChange branch, allowing for a ‘refresh‘ to sync the browsers websocket selection with the variable.
Once the required selection is refreshed into the variable it can be actioned in whatever way we wish… but first, it is all about the branching.
We could do a sequence of SELECT CASE tests and branch as appropriate, or similarly with a sequence of IF THEN tests… but both are rigid and clunky structures which result in more than double or triple the lines of code than there are actual menu options.
The code could be reduced to a single line by using an undocumented ELSE IF capability, eg:
IF choice$=”one” THEN gosub one ELSE IF choice$=”two” THEN gosub two ELSE IF choice$=”three” THEN gosub three ELSE gosub unknown
This technique is like hammers and multi-statement lines – useful… but not foolproof. So if you don’t appreciate its concise elegance, or you blame your tools for self-inflicted injuries, just do things safely by the book without needing to impose similar limits on everyone.
The book (Online Help) has an even more concise solution hiding in plain sight – the gosub variable$ feature – which allows the destination branch name to be supplied in the variable, and therefore doesn’t need any sequences of conditional ‘tests’… it simply branches to the specified label name contained in the variable.
This is the method used In the example below – any listbox$ selection will trigger an onhtmlchange event causing a jump to a subroutine called chosen:, where a refresh will sync the browser selection into our choice$ variable, then we simply issue gosub choice$ to branch to the selected subroutine label name.
Basic:
‘ Simple listbox internal menu
cls
options$ = “first,second,third,fourth” ‘comma-delimited list of menu options which correspond to same-name subroutine branches
choice$ = “” ‘variable to hold the menu selection
message$ = “” ’empty variable for displaying selection confirmation message
a$ = textbox$(message$) + “<br><br>”
a$ = a$ + listbox$(choice$,options$) + “<br><br>”
html a$
onhtmlchange chosen
wait
chosen:
refresh
gosub choice$
return
first:
message$ = “First was chosen”
refresh
return
second:
message$ = “Chose Second this time”
refresh
return
third:
message$ = “Not first or second…”
refresh
return
fourth:
message$ = “Oh no, Last again”
refresh
return
‘———– End ————
The script example above displays the listbox shown on the right which has an initially blank textbox above it.
Selecting a listbox option causes an onhtmlchange branch to chosen:, which issues a gosub to the selected label name.
The selected branch merely selects different text which it refreshes to the browser textbox as visible proof of the selection.
Obviously in practice the selected branch could do anything it is coded for.
In the example, the menu options are supplied in a comma-delimited option$ variable rather than be hard-coded into the listbox.
Another possibility is to load them from external text file (eg: “Menu.txt“), which is easy to create and save and view from the Editor.
This might be useful for offering the same options to different scripts, perhaps for choosing background colours etc.
The menu example above branches to execute code in internal subroutines, but it is even easier to create a menu which loads and executes external scripts.
In the following example a listbox selection still triggers a branch to ‘chosen’, but instead of using ‘gosub chosen$’ to branch internally, it uses ‘BAS.LOAD chosen$‘ to run an external file. You need to ensure that those external files do actually exist of course, but for this example demonstration I have made things easy for you by including an extra line in the script which tests if the required files do already exist, and if not, branches to a makefiles subroutine which will create them.
So don’t forget to remove “/file1.bas” to “/file4.bas” later.
Basic:
‘ Simple external file menu
if file.exists(“/file4.bas”) = 0 then gosub makefiles ‘only added for user convenience, is not necessary for the menu
cls
options$ = “/file1.bas,/file2.bas,/file3.bas,/file4.bas” ‘comma-delimited list of menu options which correspond to exterrnal filenames
choice$ = “” ‘variable to hold the menu selection
message$ = “Main menu” ‘the current menu file
a$ = textbox$(message$) + “<br><br>”
a$ = a$ + listbox$(choice$,options$) + “<br><br>”
html a$
onhtmlchange chosen
wait
chosen:
refresh
if bas.load choice$ <> 0 then message$ = “Unable to load selected file”
refresh
return
makefiles: ‘only added for user convenience, is not necessary for the menu
for c = 1 to 4
filename$ = “/file” + str$(c) + “.bas” ‘how to expand a single word into a fully qualified filename
b$ = |cls| + chr$(10)
b$ = b$ + |options$ = “/file1.bas,/file2.bas,/file3.bas,/file4.bas”| + chr$(10)
b$ = b$ + |choice$ = “”| + chr$(10)
b$ = b$ + |message$ = “This is ” + bas.filename$ | + chr$(10)
b$ = b$ + |a$ = textbox$(message$) + “<br><br>”| + chr$(10)
b$ = b$ + |a$ = a$ + listbox$(choice$,options$) + “<br><br>”| + chr$(10)
b$ = b$ + |html a$| + chr$(10)
b$ = b$ + |onhtmlchange chosen| + chr$(10)
b$ = b$ + |wait| + chr$(10)
b$ = b$ + |chosen:| + chr$(10)
b$ = b$ + |refresh| + chr$(10)
b$ = b$ + |if bas.load choice$<>0 then message$=”Unable to load selected file”| + chr$(10)
b$ = b$ + |refresh| + chr$(10)
b$ = b$ + |return| + chr$(10)
file.save filename$, b$
pause 200
next c
return
‘———– End ————
All the menu option$ filenames consisted of fully-qualified root-relative /path/filename.ext
This is necessary because all files are referenced to the root “/” rather than the current directory (folder).
The addition of the excellent bas,load feature makes it an issue to be aware of, and also know how to deal with it.
To show the issue, save and run the following script into the “/program/” folder (call it something like /program/test.bas)
filename$ = “findme.bas”
content$ = “some content”
file.save filename$, content$
a$ = “file not found”
pause 100
a$ = file.read$(filename$)
wlog a$
Notice that although filename$ contains only the filename without any path, it was saved in the root “/”, not the current folder (check where it is using File Manager).
And similarly if trying to read a file by filename alone without a path, it will be looked for in the “/” root, so will not be found from inside any current folder.
This has important implications for eg: a menu intended to read and display files that exist in the current folder to run a selected file… because it cannot be done by filename alone. All files, wherever they are, must be prefixed by the full root-relative path to their current folder… and to discover the current folder you must use bas.filename$ and extract the path from it (or extract the filename to leave just the path) which can be done something like this (there may be better ways)….
a$ = bas.filename$
wlog a$
path = word.count(a$,”/”)
filename$ = word$(a$,path,”/”)
path$ = replace$(a$,filename$,””)
wlog filename$
wlog path$
So the file menu example above used full path+filename+ext only for for simplicity, and saved them into root to ensure they work whatever the current folder.
But that approach has the limitation of hard-coding everything to one folder which is restrictive.
It also imposes longer names to be displayed in the menu, which may not matter too much in a web-based display, but can make them much harder to read on eg: a small OLED display, either because the crucial right-hand filename has been truncated, or because of having to use a much smaller font .
A more practical and satisfying solution would extract just the file
names to display in the menu, then recombine the selected name with path and extension, eg:
filename$ = path$ + choice$ + ext$
The individual name and extension components are easily obtained after the previous path/filename snippet using eg:…
name$ = word$(filename$,1,”.“)
ext$ = “.” + word$(filename$,2,”.“) ‘(the dot separator needs to be replaced after being ignored and stripped out as the chopping position character)