|
Forum Index : Microcontroller and PC projects : Pi-comite: full webserver in MMBasic
| Page 1 of 2 |
|||||
| Author | Message | ||||
| matherp Guru Joined: 11/12/2012 Location: United KingdomPosts: 10565 |
Please find attached version 5.3.a29 of the Pi-cromite software 2017-05-14_113836_mmbasic.zip This now includes the full capability to program fairly sophisticated webserver applications with stylesheets, embedded pictures, and favicons entirely in MMBasic ![]() HTML pages can be written and stored as files. These can have MMBasic variable names included within the file in {} brackets and then these will be filled in from the current values in the MMBasic program when the page is requested. To explain this an example is probably best. Here is the simple HTML file used to create the image above. <body> <h2 align='left'> HTML DEMO</h2> <link href="styles.css" rel="stylesheet" type="text/css"> <br> <img src="tiger-3.jpg" alt="mypic"> <p> <TABLE BORDER='1' CELLSPACING='0' CELLPADDING='5'> <TR> <TD></TD> <TD>Value</TD> </TR> <TR> <TD>INTEGER</TD><TD>{tcurrent}</TD> </TR> <TR> <TD>FLOAT (array)</TD><TD>{tmax(2)}</TD> </TR> <TR> <TD>STRING</TD><TD>{tmin}</TD> </TR> <TR> <TD>MISSING</TD><TD>{fred}</TD> </TR> </TABLE> </p> </body> </html> In it there are four MMBasic variables referenced: tcurrent, tmax(2), tmin, and fred In my test program (full version below) these are defined as follows: Option explicit Option default none Dim integer tcurrent=44 Dim float tmax(3)=(19.6, 22.4, 111.9,88.6) Dim string tmin="A string" Note that "fred" is not defined and I am using option explicit to display the page from MMBasic we simply use TRANSMIT PAGE "filename" The MMBasic software will automatically create a correct HTTP 1.1 header and fill in the page with the current values of the variables. Missing variables will be ignored. Formatting of numeric variables is exactly the same as would be done by a PRINT statement. As usual STR$ can be used for more specific formatting. Both the Pi 3 and Pi zero W have full wifi capability so no additional hardware is needed to run the code and connect to the web. More details of the MMBasic support for building a webserver are given in this thread The full MMBasic program to serve the example page, together with the image, css file, and favicon are included in the zip 2017-05-14_104243_html.zip The Basic code is very simple, a single Basic interrupt routine is triggered by an HTTP request and this then transmits the relevant information to the client Sub myint 'Load the request into a long string Do While Not Eof(2) LongString append a%(), Input$(10,2) Loop 'Filter out the specific request p%=LInStr(a%(),"GET",1) t%=LInStr(a%(),"HTTP",1) s$=LGetStr$(a%(),p%,t%-p%) Print s$ LongString clear a%() ' Execute the relevant request If Instr(s$,"favicon") Then Transmit FILE "favicon.ico","image/vnd.microsoft.icon" Else If Instr(s$,"styles") Then Transmit FILE "styles.css","text/css" Else If Instr(s$,"tiger-3.jpg") Then Transmit file "tiger-3.jpg","image/jpeg" Else Transmit PAGE "index.html" EndIf End Sub |
||||
| lizby Guru Joined: 17/05/2016 Location: United StatesPosts: 3464 |
This is sweet. Many thanks for all your work. PicoMite, Armmite F4, SensorKits, MMBasic Hardware, Games, etc. on fruitoftheshed |
||||
MicroBlocks![]() Guru Joined: 12/05/2012 Location: ThailandPosts: 2209 |
That is perfect. Will this also work? [code] TRANSMIT FILE, "data.csv", "text/csv" [/code] data.csv file contents: [code] Current,Maximum,Minimum,Name {tcurrent},{tmax(2)},{tmin},{fred} [/code] You sample page has some little formatting errors. It should be like this: [code] <!DOCTYPE html> <html> <head> <title>Test Page</title> <link href="styles.css" rel="stylesheet" type="text/css" /> </head> <body> <h2 align='left'> HTML DEMO</h2> <br /> <img src="tiger-3.jpg" alt="mypic" /> <br /> <TABLE BORDER='1' CELLSPACING='0' CELLPADDING='5'> <TR> <TD> </TD> <TD>Value</TD> </TR> <TR> <TD>INTEGER</TD> <TD>{tcurrent}</TD> </TR> <TR> <TD>FLOAT (array)</TD> <TD>{tmax(2)}</TD> </TR> <TR> <TD>STRING</TD> <TD>{tmin}</TD> </TR> <TR> <TD>MISSING</TD> <TD>{fred}</TD> </TR> </TABLE> </body> </html> [/code] It is not about the indentation, html does not care about that. There were a few items in the wrong place and some tags did not have the / closing tag. Next challenge? :), posting data to a webserver? Lots of IOT device send data to cloud services to generate graphs, and historical data. Microblocks. Build with logic. |
||||
| matherp Guru Joined: 11/12/2012 Location: United KingdomPosts: 10565 |
No but this will in version 5.3.a30 2017-05-14_152718_mmbasic.zip The second parameter is optional on TRANSMIT PAGE, if omitted then a html header will be sent TRANSMIT FILE sends a file without local buffering so no length restrictions TRANSMIT PAGE constructs a file in memory with embedded variables, currently maximum length 65535 bytes You should be able to look at the demo on a Pi Zero W at this address. Let me know if it works |
||||
| lizby Guru Joined: 17/05/2016 Location: United StatesPosts: 3464 |
That page works for me. When I "view source", this is what I see. [code] <!DOCTYPE html> <html> <head> <title>Test Page</title> <link href="styles.css" rel="stylesheet" type="text/css" /> </head> <body> <h2 align='left'> HTML DEMO</h2> <br /> <img src="tiger-3.jpg" alt="mypic" /> <br /> <TABLE BORDER='1' CELLSPACING='0' CELLPADDING='5'> <TR> <TD> </TD> <TD>Value</TD> </TR> <TR> <TD>INTEGER</TD> <TD>714396</TD> </TR> <TR> <TD>FLOAT (array)</TD> <TD>0.139801</TD> </TR> <TR> <TD>STRING</TD> <TD>22:13:44</TD> </TR> <TR> <TD>MISSING</TD> <TD></TD> </TR> </TABLE> </body> </html> [/code] PicoMite, Armmite F4, SensorKits, MMBasic Hardware, Games, etc. on fruitoftheshed |
||||
| panky Guru Joined: 02/10/2012 Location: AustraliaPosts: 1116 |
Terrific work Peter, you continue to amaze! Do you have any plans to impliment this functionality in your Extreme or an MM plus using an 8266? Doug. ... almost all of the Maximites, the MicromMites, the MM Extremes, the ArmMites, the PicoMite and loving it! |
||||
MicroBlocks![]() Guru Joined: 12/05/2012 Location: ThailandPosts: 2209 |
The page works. However when i try to use an URL that refers to the image directly it does not. Seems to hang. Also the page that previously worked does then not work anymore. I suspect the program stopped with an error. The url i used was the one you posted with /tiger-3.jpg added. I like the way it is done. With an interrupt to handle the request the main program does not have to care about it. Very sleek! Microblocks. Build with logic. |
||||
| matherp Guru Joined: 11/12/2012 Location: United KingdomPosts: 10565 |
The program need to have proper error checking - it was just put together to show the sysntax QUESTION: I could allow MMBasic expressions or function calls to be used in the html e.g. {str$(avar * bvar,10,4)} The downside is that if "avar" doesn't exist you will get an error and the program will stop. By using simple variables I am able to trap this and prevent an error. Thoughts anyone? |
||||
MicroBlocks![]() Guru Joined: 12/05/2012 Location: ThailandPosts: 2209 |
I would use only a variable name not expressions and functions, with maybe a formatting string as used in the printf statements in C. This will allow great control about visualization at the place you need it (in the HTML). [code] {avr:%.2f} [/code] Please don't forget the ability to add HTTP response headers to the TRANSMIT command. These are of great value. Microblocks. Build with logic. |
||||
| matherp Guru Joined: 11/12/2012 Location: United KingdomPosts: 10565 |
Attached is 5.3.a31 2017-05-15_161857_mmbasic.zip This has the following changes: ADDED: LONGSTRING TRIM nnnn This removes nnnn characters from the left of a long string. This is very useful for processing incoming datastreams such as net traffic or GPS. The attached code snippet is for finding the next HTTP request but the concept can be used for any datastream Do While Not Eof(2) 'if anything to process append it to the long string LongString append a%(), Input$(10,2) Loop p%=LInStr(a%(),"GET",1) 'identify the start of what we are interested in t%=LInStr(a%(),"HTTP",1) 'identify the end of what we are interested in s$=LGetStr$(a%(),p%,t%-p%+4) 'get the interesting data as a normal string LongString trim a%(),t%+4 'strip this from the front of the long string CHANGED: Fixed another bug in TIMER to avoid it reporting negative numbers Processing mode changed to "real-time" in one-wire execution. This fixes a bug seen on the Pi-zero where one-wire processing (including DS18B20) would occasionally return invalid values. My webserver test program incorporating these changes, some better error checking, and checking for invalid page requests is now: Dim float tmax(3)=(19.6, 22.4, 111.9,88.6) Dim string tmin="A string" Open "socket,myint,100" As #2 Do TEMPR START 11 tcurrent=Timer tmin=Time$ Pause 500 tmax(2)=TEMPR(11) Loop Sub myint 'print the request - normally we would parse it Do While Not Eof(2) LongString append a%(), Input$(10,2) Loop p%=LInStr(a%(),"GET",1) t%=LInStr(a%(),"HTTP",1) if p% and t% then s$=LGetStr$(a%(),p%,t%-p%+4) LongString trim a%(),t%+4 Print LLen(a%()),s$ ' If Instr(s$,"favicon") Then Transmit FILE "favicon.ico","image/vnd.microsoft.icon" Else If Instr(s$,"styles") Then Transmit FILE "styles.css","text/css" Else If Instr(s$,"tiger-3.jpg") Then Transmit file "tiger-3.jpg","image/jpeg" Else If Instr(s$,"GET / HTTP") Then Transmit page "index.html" Else Transmit code 404 EndIf endif end sub |
||||
MicroBlocks![]() Guru Joined: 12/05/2012 Location: ThailandPosts: 2209 |
That webserver functionality now works a lot better. I get the 404 when a unknown file is requested and it does not hang anymore. Good work! Microblocks. Build with logic. |
||||
| matherp Guru Joined: 11/12/2012 Location: United KingdomPosts: 10565 |
There is a logic bug in the above program and it is one which has caught me before and yet I still wasted an hour on it this afternoon. if p% and t% then I wanted this to say: "if p% isn't zero and t% isn't zero then do something. BUT IT DOESN'T What it actually says is: "if the bitwise AND of p% and t% isn't zero then do something" Consider p%=1 and t%=2, the bitwise AND is zero and therefore it fails the test whereas I wanted it to pass and with a logical AND it would. In C "&" is a bitwise AND. A logical AND is "&&" so the two are differentiated. The moral of the tale is always use = and <> if you want a logical AND so: if p%<>0 and t%<>0 then works perfectly Once this is solved attached is a simple program for a remote controlled thermostat to run on a Pi in MMBasic. You can have a look at the application here for the next few hours. The security code is "123456" which is needed to reset the max/min or change the thermostat setting [code] option explicit option default none Const starttemp = 18 Const maxargs = 32 Const relaypin=7 Const off=0 Const on=1 Dim a%(1000),i% Dim integer sp(11) Dim s$,pg$ Dim security$="123456" Dim string cp(11) Dim float tcurrent,tnew Dim float tmax=-1000 Dim float tmin=1000 Dim arg$(1,maxargs-1) Dim integer checked=0 Const check$="checked='checked'" 'string to set a readio button pressed Dim string heating="off" Const hon$="#ff0000" 'red Const hoff$="#00ff00" 'green Dim string hcol=hoff$ ' SetPin relaypin,dout For i%=0 To 10 sp(i%)=i%+starttemp-1 'set up the temperature values cp(i%)="" 'set up the radio buttons as not pressed Next i% tcurrent=TEMPR(11) 'get the current temperature cp(checked)=check$ Open "socket,myint,100" As #2 Do TEMPR START 11 Pause 2000 tnew=TEMPR(11) If Abs(tnew-tcurrent)<10 Then tcurrent=tnew updateheater If tcurrent>tmax Then tmax=tcurrent If tcurrent<tmin Then tmin=tcurrent Loop ' Sub myint Local p%=0, t%=0 Local g$ Do While Not Eof(2) LongString append a%(),Input$(10,2) Loop p%=LInStr(a%(),"GET",1) t%=LInStr(a%(),"HTTP",1) If p%<>0 And t%<>0 Then 'full request received s$=LGetStr$(a%(),p%,t%-p%+4) LongString trim a%(),t%+4 pg$= parserequest$(s$,i%) If i% Then If arg$(0,0)="Security" And arg$(1,0)=security$ Then 'valid update If arg$(0,1)="R" Or arg$(0,2)="R" Then cp(checked)="" If arg$(0,1)="R" Then checked=Asc(arg$(1,1))-Asc("A") If arg$(0,2)="R" Then checked=Asc(arg$(1,2))-Asc("A") cp(checked)=check$ updateheater EndIf If arg$(0,1)="Check" Or arg$(0,2)="Check" Then tmin=tcurrent tmax=tcurrent EndIf EndIf EndIf ' If pg$="index.html" Then Transmit page "index.html" Else Transmit code 404 EndIf EndIf End Sub 'Function to parse an HTML GET request' ' Assumes that the request starts with "GET /" ' and ends with "HTTP" ' Function parserequest$(req$, paramcount As integer) Local a$,b$ Local integer inpos,startparam,processargs For inpos=0 To maxargs-1 arg$(0,inpos)="" arg$(1,inpos)="" Next inpos paramcount=0 a$=Mid$(req$,6,Len(req$)-10) inpos=Instr(a$,"?") If inpos<>0 Then 'parameters found processargs=1 parserequest$=Left$(a$,inpos-1) a$=Mid$(a$,inpos+1) Do arg$(0,paramcount)="" arg$(1,paramcount)="" inpos=Instr(a$,"=") startparam=1 arg$(0,paramcount)=Mid$(a$,startparam,inpos-startparam) startparam=inpos+1 inpos=Instr(a$,"&") If inpos<>0 Then arg$(1,paramcount)=Mid$(a$,startparam,inpos-startparam) a$=Mid$(a$,inpos+1) paramcount=paramcount+1 Else arg$(1,paramcount)=Mid$(a$,startparam) paramcount=paramcount+1 processargs=0 EndIf Loop While processargs Else parserequest$=a$ EndIf If a$="" Then parserequest$="index" EndIf If Instr(parserequest$,".html")=0 And Instr(parserequest$,".HTML")=0 Then parserequest$=parserequest$+".html" End Function Sub updateheater Local float hcalc If checked=11 Then Pin(relaypin)=1 heating="On" hcol=hon$ EndIf If checked=0 Then Pin(relaypin)=0 heating="Off" hcol=hoff$ EndIf If checked>=1 And checked<=10 Then hcalc=checked+starttemp-1 'setpoint temperature If tcurrent>hcalc Then 'turn heating off Pin(relaypin)=0 heating="Off" hcol=hoff$ EndIf If tcurrent<hcalc Then 'turn heating on Pin(relaypin)=1 heating="On" hcol=hon$ EndIf EndIf End Sub and the HTML including the variable substitution strings <html> <head> <title>Remote Thermostat</title> </head> <body> <form name='f1' method='get' action='index'> <h2 align='left'>Remote Thermostat V4.0</h2> Update Code: <input type='text' name='Security' size='6' value='000000'> <br> <p> <TABLE BORDER='1' CELLSPACING='0' CELLPADDING='5'> <TR> <TD>Heating</TD> <TD BGCOLOR='{HCOL}'>{heating}</TD> </TR> </TABLE> </p> <p> <TABLE BORDER='1' CELLSPACING='0' CELLPADDING='5'> <TR> <TD></TD> <TD>Temperature</TD> </TR> <TR> <TD>Current</TD> <TD>{TCURRENT}°C</TD> </TR> <TR> <TD>Max</TD> <TD>{TMAX}°C</TD> </TR> <TR> <TD>Min</TD> <TD>{TMIN}°C</TD> </TR> </TABLE> </p> <input name='Check' type='checkbox' value='Reset' onClick='this.form.submit()'> Reset Max/Min<p> <TABLE BORDER='1' CELLSPACING='0' CELLPADDING='5'> <TR> <TD>Thermostat</TD> <TD><input name='R' type='radio' {CP(0)} value='A' onClick='this.form.submit()'> Off<br></TD> <TD><input name='R' type='radio' {CP(1)} value='B' onClick='this.form.submit()'> {SP(1)}°C<br></TD> <TD><input name='R' type='radio' {CP(2)} value='C' onClick='this.form.submit()'> {SP(2)}°C<br></TD> <TD><input name='R' type='radio' {CP(3)} value='D' onClick='this.form.submit()'> {SP(3)}°C<br></TD> <TD><input name='R' type='radio' {CP(4)} value='E' onClick='this.form.submit()'> {SP(4)}°C<br></TD> <TD><input name='R' type='radio' {CP(5)} value='F' onClick='this.form.submit()'> {SP(5)}°C<br></TD> <TD><input name='R' type='radio' {CP(6)} value='G' onClick='this.form.submit()'> {SP(6)}°C<br></TD> <TD><input name='R' type='radio' {CP(7)} value='H' onClick='this.form.submit()'> {SP(7)}°C<br></TD> <TD><input name='R' type='radio' {CP(8)} value='I' onClick='this.form.submit()'> {SP(8)}°C<br></TD> <TD><input name='R' type='radio' {CP(9)} value='J' onClick='this.form.submit()'> {SP(9)}°C<br></TD> <TD><input name='R' type='radio' {CP(10)} value='K' onClick='this.form.submit()'> {SP(10)}°C<br></TD> <TD><input name='R' type='radio' {CP(11)} value='L' onClick='this.form.submit()'> On<br></TD> </TR> </TABLE> </p> </form> </body> </html> |
||||
| RonnS Senior Member Joined: 16/07/2015 Location: GermanyPosts: 121 |
Hi Peter, everything works. I use WINSCP for convenient editing of the files and a wireless keyboard to control the PI so I can see changes and results immediately on TV Thanks for the examples, which are good for beginners with HTML regards Ron |
||||
| led-bloon Senior Member Joined: 21/12/2014 Location: AustraliaPosts: 208 |
Peter Could the following modification be made in your code: <code snippet> Sub MyInt Local f$, i% s$= ..... get the GET request .... ' i% = Instr(s$, "/")+1 f$ = Mid$(s$,i%,Len(s$)-5) ' extract the file plus extension string ' If Instr(f$,".ico") Then ' Icon file? Transmit FILE f$,"image/vnd.microsoft.icon" '------------^^^ Else If Instr(f$,".css") Then ' style sheet file? Transmit FILE f$,"text/css" ' Use f$ for the file name '------------^^^ Else If Instr(f$,".jpg") Then Transmit FILE f$,"image/jpeg" Else If Instr(f$,".htm") Then Transmit page f$ Else Transmit CODE 404 EndIf Multiple jpgs or gifs or pngs could appear on one page without having to name each file in this code. Also: Has Transmit CODE been deleted as it doesn't appear to work with FireFox? I just get a blank screen Finally: Great work and thanks! don Miss you George |
||||
| WhiteWizzard Guru Joined: 05/04/2013 Location: United KingdomPosts: 2959 |
People across the planet - please help me test something! I am trying to do something based on Peter's demo Thermostat. To ensure I have things set-up correctly can anyone out there with a couple of spare minutes log onto the following URL: whitewizzard.ddns.net:1234 Hopefully you will see the familiar Thermostat webpage. If so, can you then simply click between 27'C and ON (no need to enter an Update code - just click/toggle between 27'c and ON) Thanks WW |
||||
| OA47 Guru Joined: 11/04/2012 Location: AustraliaPosts: 1013 |
WW tried to logon using Msoft Edge on W10 but timed out. Graeme |
||||
| WhiteWizzard Guru Joined: 05/04/2013 Location: United KingdomPosts: 2959 |
Thanks OA47 Please, if you have time, can you try my WAN IP instead as follows: 151.228.246.21:1234 Thanks . . . |
||||
| OA47 Guru Joined: 11/04/2012 Location: AustraliaPosts: 1013 |
ww, I can ping the address ok but still no response from browser. Graeme |
||||
| WhiteWizzard Guru Joined: 05/04/2013 Location: United KingdomPosts: 2959 |
I just done the 'traditional' switch-it-off-and-back-on-again trick! Getting a faster response now here - any different there now? EDIT: Do you have port blocking on your router? Port 1234 will need to be 'open' |
||||
| WhiteWizzard Guru Joined: 05/04/2013 Location: United KingdomPosts: 2959 |
Someone is out there I can see you! |
||||
| Page 1 of 2 |
|||||
| The Back Shed's forum code is written, and hosted, in Australia. | © JAQ Software 2025 |