Safety - subtilitas/rcbench GitHub Wiki

Safety

English ยท Deutsch

How the bench stops, the external circuit the design requires, and the behaviours that are deliberate.

The project is under development and its functions are not verified on hardware.

Stop mechanisms

Mechanism Covers State
Heartbeat stops the panel is wedged, reset, browned out or unplugged; a pressed STOP generated and monitored at both ends; the monostable it gates is not fitted
Coprocessor silence watchdog, 200 ms the link is dead in either direction built and tested
STOP command over the link a deliberate stop, acknowledged and reported written

A pressed STOP stops the heartbeat, switches the panel's own output model to SAFE and writes ARM = 0 to the control page. While the link is up the panel writes ARM and THROTTLE at every 50 ms poll; an explicit switch to ARMED waits until the coprocessor reports the heartbeat trusted, writes CLEAR (0x5AFE) on its own, then ARM, THROTTLE and MOTOR_POLES in one frame, and a NACK (negative acknowledge) from the coprocessor to either leaves the panel SAFE.

Required external circuit: the monostable

The safety line is panel GPIO6 (general-purpose input/output pin 6) on header J8 (3V3, GND, GPIO6). It carries edges, not a level. The coprocessor's output enable and the servo and ESC (electronic speed controller) power path must be gated by a retriggerable monostable that stays energised only while edges keep arriving. A crash, a wedged task, a reset, a brown-out and an unplugged cable then all produce the same result: no edges, no output, independent of firmware at both ends.

Monostable window: about 150 ms. The heartbeat comes from the panel's control task, which runs every 5 ms on the core that does not draw, so its period does not depend on what a frame costs. The window stays at 150 ms, inside the coprocessor's 200 ms link failsafe, rather than tightening to the new period: the margin is what survives a task that is late, and nothing is gained by removing it.

The monostable is on no board. On the bring-up bench the edges reach the coprocessor's GP3 by a direct wire from J8, which is what lets that bench switch to ARMED.

The monostable is retriggered by the panel's edges and removes the outputs when they stop. What it adds over the direct wire is that it does this without the coprocessor's firmware doing anything. Three cases, and the middle one is the gap:

Direct wire and firmware With the monostable
The panel stops beating, the coprocessor is healthy switches to SAFE after HEARTBEAT_MAX_GAP_MS the same, and sooner or later by its own timing
The panel stops beating and the coprocessor cannot act -- wedged, or not servicing its monitor nothing switches to SAFE; the bank goes on driving the outputs are removed anyway
The panel is healthy and the coprocessor misbehaves the panel's STOP and the link watchdog are what is left no help: the panel is still beating, so the monostable stays energised

The third row is not a case the monostable is for, and no hardware in this design covers it. Until the part is fitted, the second row is uncovered too, and firmware at both ends is the whole of the interlock.

Heartbeat monitor

The coprocessor also checks the heartbeat in firmware, because a monostable cannot distinguish a heartbeat from noise. Constants from heartbeat.h:

Value
Panel edge interval 20 ms HEARTBEAT_PERIOD_MS, one edge every 20 ms, 4 control-task periods of 5 ms, not one per frame
Accepted interval 4โ€“150 ms shorter is noise; longer means the loop that owns STOP has stopped
Good intervals before the line is trusted 4 80 ms at the panel's rate

The check is asymmetric: four good intervals before the line is trusted, one bad interval or one silent window to distrust it. The coprocessor refuses to switch to ARMED while the line is not trusted, and switches its outputs to SAFE and sets its arm latch when it stops.

The heartbeat is generated by the control task, which owns STOP and runs every 5 ms on core 1, the core that does not draw. It is not generated by a timer or a peripheral, and not by the loop that draws: a panel that has stopped drawing can still be stopped. The touch controller is read by a task of its own, priority 5 on core 0, the core that draws. The control task takes that task's events from a queue and judges the controller by the time since it last answered: 500 ms without an answer switches the bench to SAFE and stops the heartbeat. The coprocessor's input is pulled down, so an unpowered or unplugged panel reads as a line that is not edging.

Arm latch

The coprocessor holds an arm latch. While it is set, a write of ARM is refused with NOT_ARMED and the bank stays SAFE whatever the ARM register holds.

Sets the latch Condition
A coprocessor start power-on, a reset, the RUN pin, a reflash
Link silence 200 ms without a request, together with the link failsafe
The heartbeat is distrusted one interval under 4 ms or over 150 ms, or 150 ms without an edge
A refused ARM an ARM answered NOT_ARMED, so a CLEAR followed by a refused ARM leaves the latch set

One thing clears it: a write of 0x5AFE to CLEAR on the control page. The panel writes CLEAR in one place, the switch to ARMED an operator completes by holding ARM for 2 s. The ARM the panel writes at every 50 ms poll while the bench is ARMED carries no CLEAR, so it never brings a latched coprocessor back.

The latch is separate from the link failsafe. The supply's ON and the STATUS page read the failsafe and the heartbeat and not the latch: after a start STATUS reads idle with no fault bit, and a supply ON is taken before the first switch to ARMED. The latch is not on STATUS. A latched coprocessor shows as the refused ARM, which the panel reports as coprocessor switched to SAFE -- arm again.

The panel has its own rule for the same events. When the coprocessor stops answering while the bench is ARMED, or while a switch to ARMED is waiting for the line, the panel latches a stop on that poll: its own bank is switched to SAFE, the throttle goes to zero, a held servo position is let go and the heartbeat stops. A missing answer is noticed after the 1000 ms exchange timeout, or at once when the panel's CAN (Controller Area Network) controller is bus-off. Every exchange counts, not only the poll's read of the bench page: the write of ARM and THROTTLE that follows it, a page service, a servo refresh, the status read. One that ends unanswered takes the link down on the control task's next pass, and until then the panel sends nothing more on it. While the bench is ARMED or a switch to ARMED is waiting, the stop is latched as that exchange ends, without waiting for the pass. A bank in ARMED with no coprocessor connected, the simulated bench, is stopped the same way on the poll where a coprocessor first answers, before any ARM is written to it.

A coprocessor restart during a run

The coprocessor restores its output binding from flash and starts latched, with no command. The panel's bank is still ARMED for up to 1000 ms. Two cases:

  • The panel notices the outage. It latches a stop and writes ARM = 0 from then on.
  • The panel does not notice it, because the request was retransmitted until the restarted coprocessor answered. Its next write of ARM is refused with NOT_ARMED, and the panel latches a stop on that answer.

In both the outputs stay off until an operator holds ARM. Host-tested in test_safety_gate for outages of 5, 30, 50, 60, 300, 1000 and 5000 ms at 55 reset phases each and three models of the wire. How long the coprocessor takes from reset to its first answered frame is not measured.

The first switch to ARMED after a stop

The heartbeat is withheld while a stop is latched. A switch to ARMED clears the panel's latch, the heartbeat resumes, and the coprocessor's monitor needs five edges before it trusts the line: 100 to 125 ms at the control task's 5 ms period. The panel waits 100 ms, then reads the STATUS fault register once per pass until the heartbeat bit reads clear, and only then writes CLEAR and the frame that switches to ARMED. If the bit has not cleared 300 ms after the hold completed, the panel gives the switch up with coprocessor refused to switch to ARMED and writes nothing, so the coprocessor's latch stays set. An answer that comes back later than those 300 ms is not used, whatever it says.

The same 300 ms hold through the writes that follow. The panel asks the deadline again before CLEAR and before the frame that switches to ARMED, because the servo release ahead of them and the CLEAR itself can be answered late. Past the deadline before CLEAR, the switch is given up as above with nothing written. Past it after an acknowledged CLEAR, no frame is written and the panel latches a stop: the CLEAR released the coprocessor's latch and no register sets it again, so the stop withholds the heartbeat and the coprocessor sets its latch when it stops trusting the line, within 150 ms. The limit of the bound is the frame that switches to ARMED itself. It is written at or before the deadline, and its acknowledgement can arrive up to the 1000 ms exchange timeout (LINK_HOST_TIMEOUT_MS) later; the coprocessor has then switched the role it was asked for to ARMED, at rest: throttle 0 for MOTOR & ESC and PROGRAMMER, surfaces centred for SERVO. The panel does not undo it. An exchange of the switch that nobody answers -- the STATUS read, the servo release, CLEAR or the frame that switches to ARMED -- has waited the 1000 ms exchange timeout or did not reach the wire: that is the link going quiet under a switch to ARMED, and the panel latches a stop as above. A refusal is an answer and leaves the panel SAFE with no stop. With no coprocessor connected the panel switches its own bank to ARMED after the 100 ms.

Deliberate behaviours

  • STOP latches. The bench stays SAFE until it is switched to ARMED again.
  • STOP closes the OUTPUT IS ON question on SUPPLY and SERVO. Each screen counts the stops it is told of, and a question stands only under the count it was asked with, so an APPLY tapped in the same frame as STOP changes no set point, though the supply reports its output off a sample later.
  • The PD mini's wiring (SETUP INTERFACES) changes, once the module has answered, only on a state read sent after the change that shows the output off. A module that switched itself on in the 500 ms between two reads keeps its pins and so its OFF path; the coprocessor switches it off.
  • A live PD mini output whose input reads under the set point plus 0.5 V on 2 input reads in a row is switched off by the coprocessor, which tells the panel afterwards. A conservative rule chosen without a measurement: it may cut a run that would have survived. Set points are capped 0.7 V under the input voltage, 0.2 V above that line, so an input voltage dropping less than 0.2 V under load cuts nothing. An ON asked while the input voltage reads under 1.5 V is not written.
  • Switching to ARMED is a two-second hold on ARM, and the command goes when the hold completes rather than when the finger lifts. Switching to SAFE (DISARM) is a press.
  • The panel writes its own flash only while the bench is SAFE, with no switch to ARMED waiting, and with the supply output off and no ON queued. Flash auto-suspend is off, so each erase or write stalls both of the panel's cores, the control task with its heartbeat, STOP, OUTPUT OFF and the supply trips included. The rule is flash_gate_open() in shared/safety/flash_gate.c, under test_flash_gate. It holds back the settings save and the board drawing's keep. Switching to ARMED and the supply are never refused for a write: the write waits. The keep (2 erases of 4 kB and 2 writes for the RP2350-CAN's 1,560-byte drawing) runs one sector per 5 ms control pass and stops between two sectors when the bench is ARMED or the supply comes on. It resumes from the same sector on the first safe pass. Its header is erased first and written last, so a keep that is paused or cut by a power loss leaves no drawing findable for the board, and the next link-up fetches it again. The duration of one erase on the panel's flash is not measured.
  • A stick run on PROGRAMMER switches to ARMED through the same policy, after its warning (NO PROPELLER, MOTOR SECURED?) is held for 2 s. It then sets the throttle to the profile's entry position before it switches the supply on, which for most ESCs is the stick at MAX: that is how their menu is entered. The ESC then sounds its programming menu and does not drive, but in rare cases a motor may start and run, so the warning asks for a resistor load in place of the motor, or a motor mounted solid with no propeller. The hold's ARM waits one frame in the screen; a STOP or a lost touch event in that frame takes it back and ends the run, so it cannot clear the stop. STOP, a switch to SAFE, a lost link, ABORT and leaving the screen end the run with the throttle at minimum, the supply off and the bench SAFE, in one step. A run that ends as planned switches the supply off first and moves the stick once the supply itself reports the output off. Stick programming.
  • A hold is credited at most 250 ms per frame, so it spans at least eight frames with the press standing. A frame's duration is measured at its top and applied at its end, and without the cap one late frame credits a hold that began while that same frame was dispatching its touch events. Both the ARM hold and the bus-fault acknowledgement take the cap.
  • A frame that lost touch events cancels the gesture in progress. A full touch queue drops its oldest entry to take the newest, and no choice there is safe on its own: a release that never arrives leaves a screen holding a press, a press that never arrives orphans the release after it, and the movement where a finger leaves a button is what abandons the hold. The render task drains the queue from the other core, so inspecting an entry does not decide which one is removed. Instead each queue's one producer numbers every event it offers, whether or not the queue keeps it, and the consumer finds a loss as a gap in the numbers: on the first event after it, before that event is handled, or, at the end of a drain, against the number the producer published before the drain began. No count raised on one core has to be read in time on the other. The render task then tells every screen that its record of the glass is stale, not only the one on top, because an event that survived the loss can have navigated away from the screen holding the press; each screen drops any gesture in progress. That asks for nothing, exactly as letting go early does, with the one exception below. Both queues are numbered -- the driver's own event queue evicts its oldest for the same reason, and a gap the control task finds there travels to the render task as an item of its own, ahead of the events that followed it. The render task drains once more right before the frame's tick, so an event lost while the loop is running is answered in that frame rather than the next. The frame log carries the events found missing in each queue as TOUCHLOST <panel>/<driver>.
  • Every control that holds state between a press and its release cancels. MOTOR & ESC, SERVO and CAN BUS FAULT have a gesture that completes on a timer, so a lost release there switches to ARMED or acknowledges on its own; the overview's tiles, the outputs and picker screens' cells, the settings screen's keys, the log viewer's buttons, rows and DELETE question and the tab rows of MOTOR & ESC, ANALYSER and BALANCE act on the release instead, and a press left latched owns a track id the controller reuses, so a later contact that began elsewhere is taken for the missing release. The DELETE question stays open through a cancel and takes the next fresh press. HOME and STOP are the router's own gesture and it cancels those itself.
  • A contact has one owner from its press to its release, and the router decides it. A screen is handed a move or a release only for a contact whose press it was handed. A contact that comes down on the status band (the top 48 px) or on the alert band is the router's until it lifts, wherever it travels. A contact that was down when the screen changed or when touch events were lost is nobody's until it lifts and comes down again. A contact the screen owns that reaches the status band is released for the screen at the last point the screen was handed, and is the router's from there: a slider's or the dial's drag ends with the value from that point, a hold is abandoned, and the distance travelled on the band is not applied. Without that release the drag stays latched to its track id, and a later contact with the same id that crosses from the band into the body moves the value by its own distance from the first drag's origin. The release carries a mark (TOUCH_FLAG_NO_TAP), and so does the release the tracker makes for a contact that is more than 120 px from where the previous report had it. A marked release ends a press or a drag and activates no control: the finger did not lift there. Four controls take it all the same, because they stop something: STOP, DISARM on MOTOR & ESC and on SERVO, OUTPUT OFF, and START TEST while a servo test runs. STOP and the home tag answer to a press that lands on them: a contact that slides onto STOP from the body presses nothing. The table holds 5 contacts, the number the GT911 reports; a sixth is handed to no screen.
  • A control that changes a setting acts on a tap, not on the press. On SETUP a - or + key steps when the finger that pressed it lifts inside it, no more than 8 px from the press in x and in y; the list scrolls from anywhere, the keys included, and a scroll changes no value. A key held steps from 0.45 s after the press. PROGRAMMER's controls follow the same rule, DEFAULTS and the stick timing's keys included. ABORT of a stick run acts on the press, and so does every stop on the bench screens: stopping never waits for a lift that may not come.
  • The screens do not rely on the router for it. Every press ends a drag on a slider or on the SERVO dial that is still latched, wherever the press lands, and leaving SERVO ends a dial drag. SUPPLY holds one press at a time; a press with the track id of the one it holds says that contact has gone, and the held press is dropped as a touch loss drops it. Not measured on a panel: whether the GT911 gives a lone finger the same track id on successive touches.
  • A touch stream that breaks while STOP is held stops the bench. The control task owns that press independently of the screens, and the release that would have stopped the bench may be the event that went missing -- or it may arrive and satisfy neither owner, because the render side cancels the band's press for the same loss. Nothing else would stop it, and the operator has already pressed STOP. What this gives up: a press that began on STOP and would have been carried off it before lifting, which asks for nothing today, stops the bench instead.
  • Cancelling a DISARM while the bench is ARMED still switches it to SAFE. Abandoning a gesture asks for nothing, and while ARMED that is the wrong direction for one of them: DISARM is a press, so its release is the whole command, and a release lost to a full queue is a switch to SAFE the operator made and the bench never saw. ARM has already sent its command by the time the finger lifts, so an ARM cancelled part way asks for nothing, which is correct. A cancel also drops an ARM the screen has posted and the application has not yet collected. An ARM already handed to the control task carries how many times the render task had dropped gestures and the number of the last event it had taken. The control task drains the driver's queue first, then drops the ARM when the render task has dropped gestures since, or has a loss notice it had not yet taken. Once the ARM is applied, any loss -- the render task dropping gestures, or a gap in the driver's stream -- undoes it until the render task acknowledges the ARMED bench, which a frame does only when it began ARMED and found the stream whole. The control task looks inside every link exchange's wait, not only once a pass, and stops the bench from there as it does for STOP: the heartbeat drops at once and the coprocessor fails safe within 150 ms, however long the exchange backlog behind the ARM. A frame that began before the ARM was published cancels screens that still believe the bench is SAFE, which posts no DISARM, so the control task has to. After the acknowledgement a loss is seen by the screens while the bench is ARMED. Nothing switches to ARMED across a loss. A posted DISARM stays.
  • The throttle moves by how far a finger travels, not to where it lands. A press on the track commands nothing, so a touch at the far end asks for nothing; a drag across the whole track asks for the whole span, and one completed inside a 50 ms poll is a 0 to 100 % step at the pin. Sliders that command nothing dangerous, such as the servo screen's sweep speed, keep tap-to-set.
  • An output value changes only while the bench is ARMED, on MOTOR & ESC and on SERVO alike. While the bench is SAFE, no control on either screen changes the value it commands: nothing is posted, the value and its readout stay, the control is drawn dimmed and the screen reads ARM FIRST. A turn of the rotary knob made then is not kept. The refusal runs from the DISARM until the bench reports ARMED, so it covers the time between a completed ARM hold and the bench's answer. From a DISARM a screen asks for, or a STOP while the bench is ARMED, until the bench reports SAFE the controls are refused too and drawn dimmed, while the DISARM waits to be collected and after it. On SERVO a sweep, a pause, a held position and a run of the automatic test end where the DISARM or the STOP is asked, and a RELEASE in that time is sent and moves no value. A drag under way ends at an ARM, a DISARM and a stop, and a finger resting on the control moves nothing until it lifts and presses again. A value the screen has posted and the application has not collected at a DISARM or a stop is dropped.
  • Every switch to ARMED starts from the channel's rest, not from where the last run left the control.
    • MOTOR & ESC: the throttle slider, its -1 and +1 steps and the knob are the controls. A switch to SAFE returns the throttle to 0 %, and it stays there until the next switch to ARMED, so the first step after it posts 1.0 %. The ARM hold returns the slider to 0 % when it asks, and so does the change to ARMED for a switch the hold did not ask for, as for a stick run on PROGRAMMER.
    • SERVO: the dial, the knob, CENTRE and SWEEP are the controls. While SAFE, the horn and the pulse width shown stay at the position last driven. At a switch to ARMED the value becomes the surface's rest, which the pins drive once the switch has paid its release: the midpoint of the channel's endpoints, 1500 us across 1000 to 2000 us and 760 us across 660 to 860 us. The first input after a switch to ARMED starts from there.
  • RELEASE on SERVO is not a position and is taken while the bench is SAFE, where it moves no value. While ARMED it puts the pins at the rest and the value shown becomes that rest, as at a switch to ARMED: a drag under way ends, and the first input after it starts from the rest. The release that ends a run of the automatic test does the same.
  • Leaving a bench screen switches the bench to SAFE.
  • The rotary knob (an AS5600 angle sensor; SETUP, APPLICATION, Rotary knob, OFF by default) moves the throttle on MOTOR & ESC and the horn on SERVO by how far it turns, as the slider does, and the same rules hold. It never switches to ARMED: the only way is the ARM hold. On both screens a turn while the bench is SAFE moves nothing and is not kept; after a switch to ARMED the knob goes on from 0 % on MOTOR & ESC and from the rest on SERVO. Leaving the screen switches the bench to SAFE. The first reading after power-up, a reading the sensor flags (no magnet, too weak, too strong, magnitude 0), a reading that does not arrive, and a step over 90 deg between two readings each set the reference again and move nothing, so a knob that stops answering stops contributing and no last value is held as a command. A finger on the track or dial owns the value against the knob for the whole frame in which it touched the control, and on SERVO a sweep, a test run and the settings panel own the horn. A frame that lost touch events, or in which the router navigated at all, drops the knob's motion. The knob's command goes out with the next frame; if that frame finds lost touch events first, the command is withdrawn and the value returns to what it was before the knob. The knob never replaces a command other than a throttle or a horn position that is waiting for the frame: a completed ARM, a DISARM, a release and a peak reset stay, and its motion is dropped. The knob is read by a task of its own on the core the renderer uses, never by the control task, with a 5 ms bus timeout on each of the three transactions of a poll, so a sensor that holds the shared I2C bus delays the touch read by at most 15 ms per poll. The touch read is not on the heartbeat's path: the control task generates the heartbeat and does not use the I2C bus. On the Waveshare ESP32-S3 Touch LCD 7 the CH422G I/O expander answers at I2C addresses 0x20 to 0x27 and 0x30 to 0x3F, which includes the AS5600's fixed address 0x36, so an AS5600 knob cannot be read on that bus.
  • If the touch controller stops answering for 500 ms, the bench switches to SAFE and refuses to switch to ARMED. The panel is the only place a STOP button exists.
  • After a link failsafe the coprocessor does not switch back to ARMED when traffic returns. Leaving the failsafe takes a write of a defined value (0x5AFE) to the control page. The same holds after a coprocessor start and after a distrusted heartbeat: see Arm latch.
  • A link lost while ARMED is a stop at the panel. It latches like STOP and is cleared by the next switch to ARMED.
  • While the bench is ARMED the coprocessor refuses, with BAD_VALUE, an OUTPUTS write that changes a slot and a CHAN_CFG write that changes a channel's role or anything of a throttle channel. A surface's slew and pulse range are taken while ARMED: the SERVO screen writes them with every position. The panel writes a binding and the throttle's pulse range only while SAFE.
  • The coprocessor acts on overcurrent, over-temperature, stall timeout and a lost link on its own authority and reports the fault at the next poll.
  • A switch to ARMED names the roles it drives (Link, An arm for one role). MOTOR & ESC and a PROGRAMMER stick run switch the throttle outputs to ARMED, ARM 0x0002. SERVO switches the surfaces and the PROG PORT pin to ARMED, ARM 0x0004. A pin of a role the ARM does not name sends no pulse, as while SAFE. A coprocessor older than protocol 4.18 takes any non-zero ARM as the whole bank, so on it every bound pin drives.
  • A switch to ARMED for the other role goes through SAFE. The panel writes ARM = 0, waits at least 100 ms, then writes the new roles; the coprocessor refuses a change from one non-zero ARM to another with BAD_VALUE.
  • STOP, the heartbeat stopping, link silence and a switch to SAFE each zero the whole ARM, whatever roles it named, and every output stops.
  • A bench in ARMED drives every bound pin of the roles it armed, whether or not anything is commanding it. A channel that has had no command for 500 ms is rendered at its role's rest: stopped for a throttle, centred for a surface. Centred is the midpoint of that channel's own endpoints โ€” 1500 us across the default 1000 to 2000 us, and 760 us across the 660 to 860 us of a narrow servo. The timeout moves the channel to that rest and leaves the pin driving; switching to SAFE is what stops the edges.
  • A receiver output of 1500 us is about half throttle. What an ESC that has seen no pulses does when it is then handed 1500 us is not measured on this bench: it may run at about half throttle, and it may refuse to arm until it has seen a stop. A pin bound as a servo output can therefore carry a running motor.
  • The throttle does not reach a channel bound as a surface, because it commands the pins the binding calls motors. A write to the CHANNELS page does, because that page addresses channels by index rather than by role.
  • For the first 500 ms after a switch to ARMED, a channel is not at its rest. The switch stamps every channel's clock, so a command given while the bench was SAFE is not overdue and is rendered until it goes overdue. What is rendered depends on the channel's slew: with no slew the first step is the whole distance, so the channel is at that command; with a slew set, the switch to SAFE has already put it at rest and it ramps from there, covering at most slew_per_s * 500 / 1000 before the timeout takes it back. Either way it is driving, and a switch to SAFE is what stops it.
  • The Ramp limit setting, 5 to 300 %/s, governs the panel's own throttle bank. That bank is the modelled bench: its slewed value is read by the telemetry simulator and by nothing else, and only while the link is down. A coprocessor that is answering is sent the raw command instead, and outbind_to_chan_cfg() writes no slew for any channel, so a pin bound as a throttle steps to it on the next 1 ms pass. The physical throttle is not ramped, by decision: the bank ramps a throttle upward only, so a ramp on the wire would slow the rise and nothing else. STATUS.md, Not planned.

Heartbeat rather than enable level

A static enable level fails when firmware wedges with the pin high. Edges expire on their own. The period check in firmware rejects a shorted or ringing line, which a monostable alone would accept.

The panel has no wire to any output.

โš ๏ธ **GitHub.com Fallback** โš ๏ธ