diff --git a/.gitignore b/.gitignore index 508f2fa..754ab25 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,7 @@ Thumbs.db *.log *.ini config.js +NetduinoPlus-Pinout-v1.2.jpg [Bb]in [Dd]ebug*/ *.lib diff --git a/Aquarium-Controller.sln b/Aquarium-Controller.sln index 08b99fd..a74153f 100644 --- a/Aquarium-Controller.sln +++ b/Aquarium-Controller.sln @@ -1,82 +1,116 @@ - -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2010 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Controller", "Controller\Controller.csproj", "{117848BD-213C-4AE4-8F58-D27F14DAA534}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin-Logfile", "Plugin-Logfile\Plugin-Logfile.csproj", "{65B2EAC1-B841-4134-9229-B24583B46FFB}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin-Temperature", "Plugin-Temperature\Plugin-Temperature.csproj", "{79EFAD9E-C3E1-4D3F-993E-7CFF382A1C29}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin-Thingspeak", "Plugin-Thingspeak\Plugin-Thingspeak.csproj", "{94EB2D77-53BC-4086-BD6F-9F400C1FD13F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin-Relays", "Plugin-Relays\Plugin-Relays.csproj", "{E9EB4D78-EEC4-4A40-9DE4-CB1EEA20977B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebServer", "WebServer\WebServer.csproj", "{F444D76A-7C89-4AD5-B7B6-1B8293A0DC13}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin-pH", "Plugin-pH\Plugin-pH.csproj", "{428E9964-527F-48D8-B88A-8A22435F481B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin-AquariumStatus", "Plugin-AquariumStatus\Plugin-AquariumStatus.csproj", "{F0F2B76C-B9A5-4BA8-B551-5217295EF377}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Emulator|Any CPU = Emulator|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {117848BD-213C-4AE4-8F58-D27F14DAA534}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {117848BD-213C-4AE4-8F58-D27F14DAA534}.Debug|Any CPU.Build.0 = Debug|Any CPU - {117848BD-213C-4AE4-8F58-D27F14DAA534}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {117848BD-213C-4AE4-8F58-D27F14DAA534}.Emulator|Any CPU.ActiveCfg = Emulator|Any CPU - {117848BD-213C-4AE4-8F58-D27F14DAA534}.Emulator|Any CPU.Build.0 = Emulator|Any CPU - {117848BD-213C-4AE4-8F58-D27F14DAA534}.Emulator|Any CPU.Deploy.0 = Emulator|Any CPU - {117848BD-213C-4AE4-8F58-D27F14DAA534}.Release|Any CPU.ActiveCfg = Release|Any CPU - {117848BD-213C-4AE4-8F58-D27F14DAA534}.Release|Any CPU.Build.0 = Release|Any CPU - {117848BD-213C-4AE4-8F58-D27F14DAA534}.Release|Any CPU.Deploy.0 = Release|Any CPU - {65B2EAC1-B841-4134-9229-B24583B46FFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {65B2EAC1-B841-4134-9229-B24583B46FFB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {65B2EAC1-B841-4134-9229-B24583B46FFB}.Emulator|Any CPU.ActiveCfg = Emulator|Any CPU - {65B2EAC1-B841-4134-9229-B24583B46FFB}.Emulator|Any CPU.Build.0 = Emulator|Any CPU - {65B2EAC1-B841-4134-9229-B24583B46FFB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {65B2EAC1-B841-4134-9229-B24583B46FFB}.Release|Any CPU.Build.0 = Release|Any CPU - {79EFAD9E-C3E1-4D3F-993E-7CFF382A1C29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {79EFAD9E-C3E1-4D3F-993E-7CFF382A1C29}.Debug|Any CPU.Build.0 = Debug|Any CPU - {79EFAD9E-C3E1-4D3F-993E-7CFF382A1C29}.Emulator|Any CPU.ActiveCfg = Emulator|Any CPU - {79EFAD9E-C3E1-4D3F-993E-7CFF382A1C29}.Emulator|Any CPU.Build.0 = Emulator|Any CPU - {79EFAD9E-C3E1-4D3F-993E-7CFF382A1C29}.Release|Any CPU.ActiveCfg = Release|Any CPU - {79EFAD9E-C3E1-4D3F-993E-7CFF382A1C29}.Release|Any CPU.Build.0 = Release|Any CPU - {94EB2D77-53BC-4086-BD6F-9F400C1FD13F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {94EB2D77-53BC-4086-BD6F-9F400C1FD13F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {94EB2D77-53BC-4086-BD6F-9F400C1FD13F}.Emulator|Any CPU.ActiveCfg = Emulator|Any CPU - {94EB2D77-53BC-4086-BD6F-9F400C1FD13F}.Emulator|Any CPU.Build.0 = Emulator|Any CPU - {94EB2D77-53BC-4086-BD6F-9F400C1FD13F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {94EB2D77-53BC-4086-BD6F-9F400C1FD13F}.Release|Any CPU.Build.0 = Release|Any CPU - {E9EB4D78-EEC4-4A40-9DE4-CB1EEA20977B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E9EB4D78-EEC4-4A40-9DE4-CB1EEA20977B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E9EB4D78-EEC4-4A40-9DE4-CB1EEA20977B}.Emulator|Any CPU.ActiveCfg = Emulator|Any CPU - {E9EB4D78-EEC4-4A40-9DE4-CB1EEA20977B}.Emulator|Any CPU.Build.0 = Emulator|Any CPU - {E9EB4D78-EEC4-4A40-9DE4-CB1EEA20977B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E9EB4D78-EEC4-4A40-9DE4-CB1EEA20977B}.Release|Any CPU.Build.0 = Release|Any CPU - {F444D76A-7C89-4AD5-B7B6-1B8293A0DC13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F444D76A-7C89-4AD5-B7B6-1B8293A0DC13}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F444D76A-7C89-4AD5-B7B6-1B8293A0DC13}.Emulator|Any CPU.ActiveCfg = Release|Any CPU - {F444D76A-7C89-4AD5-B7B6-1B8293A0DC13}.Emulator|Any CPU.Build.0 = Release|Any CPU - {F444D76A-7C89-4AD5-B7B6-1B8293A0DC13}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F444D76A-7C89-4AD5-B7B6-1B8293A0DC13}.Release|Any CPU.Build.0 = Release|Any CPU - {428E9964-527F-48D8-B88A-8A22435F481B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {428E9964-527F-48D8-B88A-8A22435F481B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {428E9964-527F-48D8-B88A-8A22435F481B}.Emulator|Any CPU.ActiveCfg = Release|Any CPU - {428E9964-527F-48D8-B88A-8A22435F481B}.Emulator|Any CPU.Build.0 = Release|Any CPU - {428E9964-527F-48D8-B88A-8A22435F481B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {428E9964-527F-48D8-B88A-8A22435F481B}.Release|Any CPU.Build.0 = Release|Any CPU - {F0F2B76C-B9A5-4BA8-B551-5217295EF377}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F0F2B76C-B9A5-4BA8-B551-5217295EF377}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F0F2B76C-B9A5-4BA8-B551-5217295EF377}.Emulator|Any CPU.ActiveCfg = Release|Any CPU - {F0F2B76C-B9A5-4BA8-B551-5217295EF377}.Emulator|Any CPU.Build.0 = Release|Any CPU - {F0F2B76C-B9A5-4BA8-B551-5217295EF377}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F0F2B76C-B9A5-4BA8-B551-5217295EF377}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Controller", "Controller\Controller.csproj", "{117848BD-213C-4AE4-8F58-D27F14DAA534}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin-Logfile", "Plugin-Logfile\Plugin-Logfile.csproj", "{65B2EAC1-B841-4134-9229-B24583B46FFB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin-Temperature", "Plugin-Temperature\Plugin-Temperature.csproj", "{79EFAD9E-C3E1-4D3F-993E-7CFF382A1C29}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin-Thingspeak", "Plugin-Thingspeak\Plugin-Thingspeak.csproj", "{94EB2D77-53BC-4086-BD6F-9F400C1FD13F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin-Relays", "Plugin-Relays\Plugin-Relays.csproj", "{E9EB4D78-EEC4-4A40-9DE4-CB1EEA20977B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin-pH", "Plugin-pH\Plugin-pH.csproj", "{428E9964-527F-48D8-B88A-8A22435F481B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin-AquariumStatus", "Plugin-AquariumStatus\Plugin-AquariumStatus.csproj", "{F0F2B76C-B9A5-4BA8-B551-5217295EF377}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin-ElectricalConductivity", "Plugin-ElectricalConductivity\Plugin-ElectricalConductivity.csproj", "{81D8D4B0-E62D-4F66-8E66-BC26616BDC9C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin-DHTSensor", "Plugin-DHTSensor\Plugin-DHTSensor.csproj", "{D02941EE-C27E-4422-AD5E-FE99C4B822CA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin-CO2", "Plugin-CO2\Plugin-CO2.csproj", "{A71793DB-2334-4922-9139-4CCEF2B3EE31}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebServer", "WebServer\WebServer.csproj", "{71B2DDB7-063C-4866-BD82-83D06E22E225}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetduinoPlusTelnet", "NetduinoPlusTelnet\NetduinoPlusTelnet\NetduinoPlusTelnet.csproj", "{58DB2B34-17A3-448D-A0B5-A5FEBEAC6F53}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Emulator|Any CPU = Emulator|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {117848BD-213C-4AE4-8F58-D27F14DAA534}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {117848BD-213C-4AE4-8F58-D27F14DAA534}.Debug|Any CPU.Build.0 = Debug|Any CPU + {117848BD-213C-4AE4-8F58-D27F14DAA534}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {117848BD-213C-4AE4-8F58-D27F14DAA534}.Emulator|Any CPU.ActiveCfg = Emulator|Any CPU + {117848BD-213C-4AE4-8F58-D27F14DAA534}.Emulator|Any CPU.Build.0 = Emulator|Any CPU + {117848BD-213C-4AE4-8F58-D27F14DAA534}.Emulator|Any CPU.Deploy.0 = Emulator|Any CPU + {117848BD-213C-4AE4-8F58-D27F14DAA534}.Release|Any CPU.ActiveCfg = Release|Any CPU + {117848BD-213C-4AE4-8F58-D27F14DAA534}.Release|Any CPU.Build.0 = Release|Any CPU + {117848BD-213C-4AE4-8F58-D27F14DAA534}.Release|Any CPU.Deploy.0 = Release|Any CPU + {65B2EAC1-B841-4134-9229-B24583B46FFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {65B2EAC1-B841-4134-9229-B24583B46FFB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {65B2EAC1-B841-4134-9229-B24583B46FFB}.Emulator|Any CPU.ActiveCfg = Emulator|Any CPU + {65B2EAC1-B841-4134-9229-B24583B46FFB}.Emulator|Any CPU.Build.0 = Emulator|Any CPU + {65B2EAC1-B841-4134-9229-B24583B46FFB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {65B2EAC1-B841-4134-9229-B24583B46FFB}.Release|Any CPU.Build.0 = Release|Any CPU + {79EFAD9E-C3E1-4D3F-993E-7CFF382A1C29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {79EFAD9E-C3E1-4D3F-993E-7CFF382A1C29}.Debug|Any CPU.Build.0 = Debug|Any CPU + {79EFAD9E-C3E1-4D3F-993E-7CFF382A1C29}.Emulator|Any CPU.ActiveCfg = Emulator|Any CPU + {79EFAD9E-C3E1-4D3F-993E-7CFF382A1C29}.Emulator|Any CPU.Build.0 = Emulator|Any CPU + {79EFAD9E-C3E1-4D3F-993E-7CFF382A1C29}.Release|Any CPU.ActiveCfg = Release|Any CPU + {79EFAD9E-C3E1-4D3F-993E-7CFF382A1C29}.Release|Any CPU.Build.0 = Release|Any CPU + {94EB2D77-53BC-4086-BD6F-9F400C1FD13F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {94EB2D77-53BC-4086-BD6F-9F400C1FD13F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {94EB2D77-53BC-4086-BD6F-9F400C1FD13F}.Emulator|Any CPU.ActiveCfg = Emulator|Any CPU + {94EB2D77-53BC-4086-BD6F-9F400C1FD13F}.Emulator|Any CPU.Build.0 = Emulator|Any CPU + {94EB2D77-53BC-4086-BD6F-9F400C1FD13F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {94EB2D77-53BC-4086-BD6F-9F400C1FD13F}.Release|Any CPU.Build.0 = Release|Any CPU + {E9EB4D78-EEC4-4A40-9DE4-CB1EEA20977B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E9EB4D78-EEC4-4A40-9DE4-CB1EEA20977B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E9EB4D78-EEC4-4A40-9DE4-CB1EEA20977B}.Emulator|Any CPU.ActiveCfg = Emulator|Any CPU + {E9EB4D78-EEC4-4A40-9DE4-CB1EEA20977B}.Emulator|Any CPU.Build.0 = Emulator|Any CPU + {E9EB4D78-EEC4-4A40-9DE4-CB1EEA20977B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E9EB4D78-EEC4-4A40-9DE4-CB1EEA20977B}.Release|Any CPU.Build.0 = Release|Any CPU + {428E9964-527F-48D8-B88A-8A22435F481B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {428E9964-527F-48D8-B88A-8A22435F481B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {428E9964-527F-48D8-B88A-8A22435F481B}.Emulator|Any CPU.ActiveCfg = Release|Any CPU + {428E9964-527F-48D8-B88A-8A22435F481B}.Emulator|Any CPU.Build.0 = Release|Any CPU + {428E9964-527F-48D8-B88A-8A22435F481B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {428E9964-527F-48D8-B88A-8A22435F481B}.Release|Any CPU.Build.0 = Release|Any CPU + {F0F2B76C-B9A5-4BA8-B551-5217295EF377}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F0F2B76C-B9A5-4BA8-B551-5217295EF377}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F0F2B76C-B9A5-4BA8-B551-5217295EF377}.Emulator|Any CPU.ActiveCfg = Release|Any CPU + {F0F2B76C-B9A5-4BA8-B551-5217295EF377}.Emulator|Any CPU.Build.0 = Release|Any CPU + {F0F2B76C-B9A5-4BA8-B551-5217295EF377}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F0F2B76C-B9A5-4BA8-B551-5217295EF377}.Release|Any CPU.Build.0 = Release|Any CPU + {81D8D4B0-E62D-4F66-8E66-BC26616BDC9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {81D8D4B0-E62D-4F66-8E66-BC26616BDC9C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {81D8D4B0-E62D-4F66-8E66-BC26616BDC9C}.Emulator|Any CPU.ActiveCfg = Release|Any CPU + {81D8D4B0-E62D-4F66-8E66-BC26616BDC9C}.Emulator|Any CPU.Build.0 = Release|Any CPU + {81D8D4B0-E62D-4F66-8E66-BC26616BDC9C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {81D8D4B0-E62D-4F66-8E66-BC26616BDC9C}.Release|Any CPU.Build.0 = Release|Any CPU + {D02941EE-C27E-4422-AD5E-FE99C4B822CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D02941EE-C27E-4422-AD5E-FE99C4B822CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D02941EE-C27E-4422-AD5E-FE99C4B822CA}.Emulator|Any CPU.ActiveCfg = Emulator|Any CPU + {D02941EE-C27E-4422-AD5E-FE99C4B822CA}.Emulator|Any CPU.Build.0 = Emulator|Any CPU + {D02941EE-C27E-4422-AD5E-FE99C4B822CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D02941EE-C27E-4422-AD5E-FE99C4B822CA}.Release|Any CPU.Build.0 = Release|Any CPU + {A71793DB-2334-4922-9139-4CCEF2B3EE31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A71793DB-2334-4922-9139-4CCEF2B3EE31}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A71793DB-2334-4922-9139-4CCEF2B3EE31}.Emulator|Any CPU.ActiveCfg = Emulator|Any CPU + {A71793DB-2334-4922-9139-4CCEF2B3EE31}.Emulator|Any CPU.Build.0 = Emulator|Any CPU + {A71793DB-2334-4922-9139-4CCEF2B3EE31}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A71793DB-2334-4922-9139-4CCEF2B3EE31}.Release|Any CPU.Build.0 = Release|Any CPU + {71B2DDB7-063C-4866-BD82-83D06E22E225}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {71B2DDB7-063C-4866-BD82-83D06E22E225}.Debug|Any CPU.Build.0 = Debug|Any CPU + {71B2DDB7-063C-4866-BD82-83D06E22E225}.Emulator|Any CPU.ActiveCfg = Release|Any CPU + {71B2DDB7-063C-4866-BD82-83D06E22E225}.Emulator|Any CPU.Build.0 = Release|Any CPU + {71B2DDB7-063C-4866-BD82-83D06E22E225}.Release|Any CPU.ActiveCfg = Release|Any CPU + {71B2DDB7-063C-4866-BD82-83D06E22E225}.Release|Any CPU.Build.0 = Release|Any CPU + {58DB2B34-17A3-448D-A0B5-A5FEBEAC6F53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {58DB2B34-17A3-448D-A0B5-A5FEBEAC6F53}.Debug|Any CPU.Build.0 = Debug|Any CPU + {58DB2B34-17A3-448D-A0B5-A5FEBEAC6F53}.Emulator|Any CPU.ActiveCfg = Release|Any CPU + {58DB2B34-17A3-448D-A0B5-A5FEBEAC6F53}.Emulator|Any CPU.Build.0 = Release|Any CPU + {58DB2B34-17A3-448D-A0B5-A5FEBEAC6F53}.Emulator|Any CPU.Deploy.0 = Release|Any CPU + {58DB2B34-17A3-448D-A0B5-A5FEBEAC6F53}.Release|Any CPU.ActiveCfg = Release|Any CPU + {58DB2B34-17A3-448D-A0B5-A5FEBEAC6F53}.Release|Any CPU.Build.0 = Release|Any CPU + {58DB2B34-17A3-448D-A0B5-A5FEBEAC6F53}.Release|Any CPU.Deploy.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/ArduinoBridge/ArduinoBridge.ino b/ArduinoBridge/ArduinoBridge.ino new file mode 100644 index 0000000..4ad0607 --- /dev/null +++ b/ArduinoBridge/ArduinoBridge.ino @@ -0,0 +1,110 @@ +#include +#include +#include +#include +#include + +//Declare objects +dht11 AirTempSensor; +SerialCommand sCmd; + +//Settings for DHT11 Sensor +#define AIR_TEMP_PIN 2 +//Setting for CO2 Sensor +#define MG_PIN (7) //define which analog input channel you are going to use +#define BOOL_PIN (3) +#define DC_GAIN (8.5) //define the DC gain of amplifier +#define READ_SAMPLE_INTERVAL (50) //define how many samples you are going to take in normal operation +#define READ_SAMPLE_TIMES (5) //define the time interval(in milisecond) between each samples in normal operation +#define ZERO_POINT_VOLTAGE (0.220) //define the output of the sensor in volts when the concentration of CO2 is 400PPM +#define REACTION_VOLTGAE (0.020) //define the voltage drop of the sensor when move the sensor from air into 1000ppm CO2 +float CO2Curve[3] = {2.602,ZERO_POINT_VOLTAGE,(REACTION_VOLTGAE/(2.602-3))}; + //two points are taken from the curve. + //with these two points, a line is formed which is + //"approximately equivalent" to the original curve. + //data format:{ x, y, slope}; point1: (lg400, 0.324), point2: (lg4000, 0.280) + //slope = ( reaction voltage ) / (log400 ¨Clog1000) + +// Variable to store temperature and humidity readings +float air_temp_c; +float humidity; + +void setup() +{ + // Start the serial port to communicate to the PC at 115200 baud + Serial.begin(57600); + sCmd.addCommand("R", GetDHTReadings); + sCmd.addCommand("CO2",GetCO2Reading); + + pinMode(BOOL_PIN, INPUT); //set pin to input + digitalWrite(BOOL_PIN, HIGH); //turn on pullup resistors + + delay(1000); +} + +void loop() +{ + //Check for Serial Input + sCmd.readSerial(); +} + +void GetDHTReadings() +{ + int chk = AirTempSensor.read(AIR_TEMP_PIN); + humidity = AirTempSensor.humidity; + air_temp_c = AirTempSensor.temperature; + Serial.print(air_temp_c); + Serial.print(","); + Serial.print(humidity); + Serial.print('\r'); +} + +void GetCO2Reading() +{ + int percentage; + float volts; + volts = MGRead(MG_PIN); + //Serial.print( "SEN-00007:" ); + //Serial.print(volts); + //Serial.print( "V " ); + + percentage = MGGetPercentage(volts,CO2Curve); + Serial.print(percentage); + Serial.print("\r"); +} + +/***************************** MGRead ********************************************* +Input: mg_pin - analog channel +Output: output of SEN-000007 +Remarks: This function reads the output of SEN-000007 +************************************************************************************/ +float MGRead(int mg_pin) +{ + int i; + float v=0; + + for (i=0;i=ZERO_POINT_VOLTAGE) { + return -1; + } else { + return pow(10, ((volts/DC_GAIN)-pcurve[1])/pcurve[2]+pcurve[0]); + } +} diff --git a/ArduinoBridge/Libraries/DHT11/dht11.cpp b/ArduinoBridge/Libraries/DHT11/dht11.cpp new file mode 100644 index 0000000..7cc0a22 --- /dev/null +++ b/ArduinoBridge/Libraries/DHT11/dht11.cpp @@ -0,0 +1,89 @@ +// +// FILE: dht11.cpp +// VERSION: 0.4.1 +// PURPOSE: DHT11 Temperature & Humidity Sensor library for Arduino +// LICENSE: GPL v3 (http://www.gnu.org/licenses/gpl.html) +// +// DATASHEET: http://www.micro4you.com/files/sensor/DHT11.pdf +// +// HISTORY: +// George Hadjikyriacou - Original version (??) +// Mod by SimKard - Version 0.2 (24/11/2010) +// Mod by Rob Tillaart - Version 0.3 (28/03/2011) +// + added comments +// + removed all non DHT11 specific code +// + added references +// Mod by Rob Tillaart - Version 0.4 (17/03/2012) +// + added 1.0 support +// Mod by Rob Tillaart - Version 0.4.1 (19/05/2012) +// + added error codes +// + +#include "dht11.h" + +// Return values: +// DHTLIB_OK +// DHTLIB_ERROR_CHECKSUM +// DHTLIB_ERROR_TIMEOUT +int dht11::read(int pin) +{ + // BUFFER TO RECEIVE + uint8_t bits[5]; + uint8_t cnt = 7; + uint8_t idx = 0; + + // EMPTY BUFFER + for (int i=0; i< 5; i++) bits[i] = 0; + + // REQUEST SAMPLE + pinMode(pin, OUTPUT); + digitalWrite(pin, LOW); + delay(18); + digitalWrite(pin, HIGH); + delayMicroseconds(40); + pinMode(pin, INPUT); + + // ACKNOWLEDGE or TIMEOUT + unsigned int loopCnt = 10000; + while(digitalRead(pin) == LOW) + if (loopCnt-- == 0) return DHTLIB_ERROR_TIMEOUT; + + loopCnt = 10000; + while(digitalRead(pin) == HIGH) + if (loopCnt-- == 0) return DHTLIB_ERROR_TIMEOUT; + + // READ OUTPUT - 40 BITS => 5 BYTES or TIMEOUT + for (int i=0; i<40; i++) + { + loopCnt = 10000; + while(digitalRead(pin) == LOW) + if (loopCnt-- == 0) return DHTLIB_ERROR_TIMEOUT; + + unsigned long t = micros(); + + loopCnt = 10000; + while(digitalRead(pin) == HIGH) + if (loopCnt-- == 0) return DHTLIB_ERROR_TIMEOUT; + + if ((micros() - t) > 40) bits[idx] |= (1 << cnt); + if (cnt == 0) // next byte? + { + cnt = 7; // restart at MSB + idx++; // next byte! + } + else cnt--; + } + + // WRITE TO RIGHT VARS + // as bits[1] and bits[3] are allways zero they are omitted in formulas. + humidity = bits[0]; + temperature = bits[2]; + + uint8_t sum = bits[0] + bits[2]; + + if (bits[4] != sum) return DHTLIB_ERROR_CHECKSUM; + return DHTLIB_OK; +} +// +// END OF FILE +// \ No newline at end of file diff --git a/ArduinoBridge/Libraries/DHT11/dht11.h b/ArduinoBridge/Libraries/DHT11/dht11.h new file mode 100644 index 0000000..89656c0 --- /dev/null +++ b/ArduinoBridge/Libraries/DHT11/dht11.h @@ -0,0 +1,41 @@ +// +// FILE: dht11.h +// VERSION: 0.4.1 +// PURPOSE: DHT11 Temperature & Humidity Sensor library for Arduino +// LICENSE: GPL v3 (http://www.gnu.org/licenses/gpl.html) +// +// DATASHEET: http://www.micro4you.com/files/sensor/DHT11.pdf +// +// URL: http://arduino.cc/playground/Main/DHT11Lib +// +// HISTORY: +// George Hadjikyriacou - Original version +// see dht.cpp file +// + +#ifndef dht11_h +#define dht11_h + +#if defined(ARDUINO) && (ARDUINO >= 100) +#include +#else +#include +#endif + +#define DHT11LIB_VERSION "0.4.1" + +#define DHTLIB_OK 0 +#define DHTLIB_ERROR_CHECKSUM -1 +#define DHTLIB_ERROR_TIMEOUT -2 + +class dht11 +{ +public: + int read(int pin); + int humidity; + int temperature; +}; +#endif +// +// END OF FILE +// \ No newline at end of file diff --git a/ArduinoBridge/Libraries/DallasTemperature/DallasTemperature.cpp b/ArduinoBridge/Libraries/DallasTemperature/DallasTemperature.cpp new file mode 100644 index 0000000..1409701 --- /dev/null +++ b/ArduinoBridge/Libraries/DallasTemperature/DallasTemperature.cpp @@ -0,0 +1,738 @@ +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. + +// Version 3.7.2 modified on Dec 6, 2011 to support Arduino 1.0 +// See Includes... +// Modified by Jordan Hochenbaum + +#include "DallasTemperature.h" + +#if ARDUINO >= 100 + #include "Arduino.h" +#else +extern "C" { + #include "WConstants.h" +} +#endif + +DallasTemperature::DallasTemperature(OneWire* _oneWire) + #if REQUIRESALARMS + : _AlarmHandler(&defaultAlarmHandler) + #endif +{ + _wire = _oneWire; + devices = 0; + parasite = false; + bitResolution = 9; + waitForConversion = true; + checkForConversion = true; +} + +// initialise the bus +void DallasTemperature::begin(void) +{ + DeviceAddress deviceAddress; + + _wire->reset_search(); + devices = 0; // Reset the number of devices when we enumerate wire devices + + while (_wire->search(deviceAddress)) + { + if (validAddress(deviceAddress)) + { + if (!parasite && readPowerSupply(deviceAddress)) parasite = true; + + ScratchPad scratchPad; + + readScratchPad(deviceAddress, scratchPad); + + bitResolution = max(bitResolution, getResolution(deviceAddress)); + + devices++; + } + } +} + +// returns the number of devices found on the bus +uint8_t DallasTemperature::getDeviceCount(void) +{ + return devices; +} + +// returns true if address is valid +bool DallasTemperature::validAddress(uint8_t* deviceAddress) +{ + return (_wire->crc8(deviceAddress, 7) == deviceAddress[7]); +} + +// finds an address at a given index on the bus +// returns true if the device was found +bool DallasTemperature::getAddress(uint8_t* deviceAddress, uint8_t index) +{ + uint8_t depth = 0; + + _wire->reset_search(); + + while (depth <= index && _wire->search(deviceAddress)) + { + if (depth == index && validAddress(deviceAddress)) return true; + depth++; + } + + return false; +} + +// attempt to determine if the device at the given address is connected to the bus +bool DallasTemperature::isConnected(uint8_t* deviceAddress) +{ + ScratchPad scratchPad; + return isConnected(deviceAddress, scratchPad); +} + +// attempt to determine if the device at the given address is connected to the bus +// also allows for updating the read scratchpad +bool DallasTemperature::isConnected(uint8_t* deviceAddress, uint8_t* scratchPad) +{ + readScratchPad(deviceAddress, scratchPad); + return (_wire->crc8(scratchPad, 8) == scratchPad[SCRATCHPAD_CRC]); +} + +// read device's scratch pad +void DallasTemperature::readScratchPad(uint8_t* deviceAddress, uint8_t* scratchPad) +{ + // send the command + _wire->reset(); + _wire->select(deviceAddress); + _wire->write(READSCRATCH); + + // TODO => collect all comments & use simple loop + // byte 0: temperature LSB + // byte 1: temperature MSB + // byte 2: high alarm temp + // byte 3: low alarm temp + // byte 4: DS18S20: store for crc + // DS18B20 & DS1822: configuration register + // byte 5: internal use & crc + // byte 6: DS18S20: COUNT_REMAIN + // DS18B20 & DS1822: store for crc + // byte 7: DS18S20: COUNT_PER_C + // DS18B20 & DS1822: store for crc + // byte 8: SCRATCHPAD_CRC + // + // for(int i=0; i<9; i++) + // { + // scratchPad[i] = _wire->read(); + // } + + + // read the response + + // byte 0: temperature LSB + scratchPad[TEMP_LSB] = _wire->read(); + + // byte 1: temperature MSB + scratchPad[TEMP_MSB] = _wire->read(); + + // byte 2: high alarm temp + scratchPad[HIGH_ALARM_TEMP] = _wire->read(); + + // byte 3: low alarm temp + scratchPad[LOW_ALARM_TEMP] = _wire->read(); + + // byte 4: + // DS18S20: store for crc + // DS18B20 & DS1822: configuration register + scratchPad[CONFIGURATION] = _wire->read(); + + // byte 5: + // internal use & crc + scratchPad[INTERNAL_BYTE] = _wire->read(); + + // byte 6: + // DS18S20: COUNT_REMAIN + // DS18B20 & DS1822: store for crc + scratchPad[COUNT_REMAIN] = _wire->read(); + + // byte 7: + // DS18S20: COUNT_PER_C + // DS18B20 & DS1822: store for crc + scratchPad[COUNT_PER_C] = _wire->read(); + + // byte 8: + // SCTRACHPAD_CRC + scratchPad[SCRATCHPAD_CRC] = _wire->read(); + + _wire->reset(); +} + +// writes device's scratch pad +void DallasTemperature::writeScratchPad(uint8_t* deviceAddress, const uint8_t* scratchPad) +{ + _wire->reset(); + _wire->select(deviceAddress); + _wire->write(WRITESCRATCH); + _wire->write(scratchPad[HIGH_ALARM_TEMP]); // high alarm temp + _wire->write(scratchPad[LOW_ALARM_TEMP]); // low alarm temp + // DS18S20 does not use the configuration register + if (deviceAddress[0] != DS18S20MODEL) _wire->write(scratchPad[CONFIGURATION]); // configuration + _wire->reset(); + // save the newly written values to eeprom + _wire->write(COPYSCRATCH, parasite); + if (parasite) delay(10); // 10ms delay + _wire->reset(); +} + +// reads the device's power requirements +bool DallasTemperature::readPowerSupply(uint8_t* deviceAddress) +{ + bool ret = false; + _wire->reset(); + _wire->select(deviceAddress); + _wire->write(READPOWERSUPPLY); + if (_wire->read_bit() == 0) ret = true; + _wire->reset(); + return ret; +} + + +// set resolution of all devices to 9, 10, 11, or 12 bits +// if new resolution is out of range, it is constrained. +void DallasTemperature::setResolution(uint8_t newResolution) +{ + bitResolution = constrain(newResolution, 9, 12); + DeviceAddress deviceAddress; + for (int i=0; ireset(); + _wire->skip(); + _wire->write(STARTCONVO, parasite); + + // ASYNC mode? + if (!waitForConversion) return; + blockTillConversionComplete(&bitResolution, 0); + + return; +} + +// sends command for one device to perform a temperature by address +// returns FALSE if device is disconnected +// returns TRUE otherwise +bool DallasTemperature::requestTemperaturesByAddress(uint8_t* deviceAddress) +{ + + _wire->reset(); + _wire->select(deviceAddress); + _wire->write(STARTCONVO, parasite); + + // check device + ScratchPad scratchPad; + if (!isConnected(deviceAddress, scratchPad)) return false; + + + // ASYNC mode? + if (!waitForConversion) return true; + uint8_t bitResolution = getResolution(deviceAddress); + blockTillConversionComplete(&bitResolution, deviceAddress); + + return true; +} + + +void DallasTemperature::blockTillConversionComplete(uint8_t* bitResolution, uint8_t* deviceAddress) +{ + if(deviceAddress != 0 && checkForConversion && !parasite) + { + // Continue to check if the IC has responded with a temperature + // NB: Could cause issues with multiple devices (one device may respond faster) + unsigned long start = millis(); + while(!isConversionAvailable(0) && ((millis() - start) < 750)); + } + + // Wait a fix number of cycles till conversion is complete (based on IC datasheet) + switch (*bitResolution) + { + case 9: + delay(94); + break; + case 10: + delay(188); + break; + case 11: + delay(375); + break; + case 12: + default: + delay(750); + break; + } + +} + +// sends command for one device to perform a temp conversion by index +bool DallasTemperature::requestTemperaturesByIndex(uint8_t deviceIndex) +{ + DeviceAddress deviceAddress; + getAddress(deviceAddress, deviceIndex); + return requestTemperaturesByAddress(deviceAddress); +} + +// Fetch temperature for device index +float DallasTemperature::getTempCByIndex(uint8_t deviceIndex) +{ + DeviceAddress deviceAddress; + getAddress(deviceAddress, deviceIndex); + return getTempC((uint8_t*)deviceAddress); +} + +// Fetch temperature for device index +float DallasTemperature::getTempFByIndex(uint8_t deviceIndex) +{ + return toFahrenheit(getTempCByIndex(deviceIndex)); +} + +// reads scratchpad and returns the temperature in degrees C +float DallasTemperature::calculateTemperature(uint8_t* deviceAddress, uint8_t* scratchPad) +{ + int16_t rawTemperature = (((int16_t)scratchPad[TEMP_MSB]) << 8) | scratchPad[TEMP_LSB]; + + switch (deviceAddress[0]) + { + case DS18B20MODEL: + case DS1822MODEL: + switch (scratchPad[CONFIGURATION]) + { + case TEMP_12_BIT: + return (float)rawTemperature * 0.0625; + break; + case TEMP_11_BIT: + return (float)(rawTemperature >> 1) * 0.125; + break; + case TEMP_10_BIT: + return (float)(rawTemperature >> 2) * 0.25; + break; + case TEMP_9_BIT: + return (float)(rawTemperature >> 3) * 0.5; + break; + } + break; + case DS18S20MODEL: + /* + + Resolutions greater than 9 bits can be calculated using the data from + the temperature, COUNT REMAIN and COUNT PER �C registers in the + scratchpad. Note that the COUNT PER �C register is hard-wired to 16 + (10h). After reading the scratchpad, the TEMP_READ value is obtained + by truncating the 0.5�C bit (bit 0) from the temperature data. The + extended resolution temperature can then be calculated using the + following equation: + + COUNT_PER_C - COUNT_REMAIN + TEMPERATURE = TEMP_READ - 0.25 + -------------------------- + COUNT_PER_C + */ + + // Good spot. Thanks Nic Johns for your contribution + return (float)(rawTemperature >> 1) - 0.25 +((float)(scratchPad[COUNT_PER_C] - scratchPad[COUNT_REMAIN]) / (float)scratchPad[COUNT_PER_C] ); + break; + } +} + +// returns temperature in degrees C or DEVICE_DISCONNECTED if the +// device's scratch pad cannot be read successfully. +// the numeric value of DEVICE_DISCONNECTED is defined in +// DallasTemperature.h. It is a large negative number outside the +// operating range of the device +float DallasTemperature::getTempC(uint8_t* deviceAddress) +{ + // TODO: Multiple devices (up to 64) on the same bus may take + // some time to negotiate a response + // What happens in case of collision? + + ScratchPad scratchPad; + if (isConnected(deviceAddress, scratchPad)) return calculateTemperature(deviceAddress, scratchPad); + return DEVICE_DISCONNECTED; +} + +// returns temperature in degrees F +// TODO: - when getTempC returns DEVICE_DISCONNECTED +// -127 gets converted to -196.6 F +float DallasTemperature::getTempF(uint8_t* deviceAddress) +{ + return toFahrenheit(getTempC(deviceAddress)); +} + +// returns true if the bus requires parasite power +bool DallasTemperature::isParasitePowerMode(void) +{ + return parasite; +} + +#if REQUIRESALARMS + +/* + +ALARMS: + +TH and TL Register Format + +BIT 7 BIT 6 BIT 5 BIT 4 BIT 3 BIT 2 BIT 1 BIT 0 + S 2^6 2^5 2^4 2^3 2^2 2^1 2^0 + +Only bits 11 through 4 of the temperature register are used +in the TH and TL comparison since TH and TL are 8-bit +registers. If the measured temperature is lower than or equal +to TL or higher than or equal to TH, an alarm condition exists +and an alarm flag is set inside the DS18B20. This flag is +updated after every temperature measurement; therefore, if the +alarm condition goes away, the flag will be turned off after +the next temperature conversion. + +*/ + +// sets the high alarm temperature for a device in degrees celsius +// accepts a float, but the alarm resolution will ignore anything +// after a decimal point. valid range is -55C - 125C +void DallasTemperature::setHighAlarmTemp(uint8_t* deviceAddress, char celsius) +{ + // make sure the alarm temperature is within the device's range + if (celsius > 125) celsius = 125; + else if (celsius < -55) celsius = -55; + + ScratchPad scratchPad; + if (isConnected(deviceAddress, scratchPad)) + { + scratchPad[HIGH_ALARM_TEMP] = (uint8_t)celsius; + writeScratchPad(deviceAddress, scratchPad); + } +} + +// sets the low alarm temperature for a device in degreed celsius +// accepts a float, but the alarm resolution will ignore anything +// after a decimal point. valid range is -55C - 125C +void DallasTemperature::setLowAlarmTemp(uint8_t* deviceAddress, char celsius) +{ + // make sure the alarm temperature is within the device's range + if (celsius > 125) celsius = 125; + else if (celsius < -55) celsius = -55; + + ScratchPad scratchPad; + if (isConnected(deviceAddress, scratchPad)) + { + scratchPad[LOW_ALARM_TEMP] = (uint8_t)celsius; + writeScratchPad(deviceAddress, scratchPad); + } +} + +// returns a char with the current high alarm temperature or +// DEVICE_DISCONNECTED for an address +char DallasTemperature::getHighAlarmTemp(uint8_t* deviceAddress) +{ + ScratchPad scratchPad; + if (isConnected(deviceAddress, scratchPad)) return (char)scratchPad[HIGH_ALARM_TEMP]; + return DEVICE_DISCONNECTED; +} + +// returns a char with the current low alarm temperature or +// DEVICE_DISCONNECTED for an address +char DallasTemperature::getLowAlarmTemp(uint8_t* deviceAddress) +{ + ScratchPad scratchPad; + if (isConnected(deviceAddress, scratchPad)) return (char)scratchPad[LOW_ALARM_TEMP]; + return DEVICE_DISCONNECTED; +} + +// resets internal variables used for the alarm search +void DallasTemperature::resetAlarmSearch() +{ + alarmSearchJunction = -1; + alarmSearchExhausted = 0; + for(uint8_t i = 0; i < 7; i++) + alarmSearchAddress[i] = 0; +} + +// This is a modified version of the OneWire::search method. +// +// Also added the OneWire search fix documented here: +// http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1238032295 +// +// Perform an alarm search. If this function returns a '1' then it has +// enumerated the next device and you may retrieve the ROM from the +// OneWire::address variable. If there are no devices, no further +// devices, or something horrible happens in the middle of the +// enumeration then a 0 is returned. If a new device is found then +// its address is copied to newAddr. Use +// DallasTemperature::resetAlarmSearch() to start over. +bool DallasTemperature::alarmSearch(uint8_t* newAddr) +{ + uint8_t i; + char lastJunction = -1; + uint8_t done = 1; + + if (alarmSearchExhausted) return false; + if (!_wire->reset()) return false; + + // send the alarm search command + _wire->write(0xEC, 0); + + for(i = 0; i < 64; i++) + { + uint8_t a = _wire->read_bit( ); + uint8_t nota = _wire->read_bit( ); + uint8_t ibyte = i / 8; + uint8_t ibit = 1 << (i & 7); + + // I don't think this should happen, this means nothing responded, but maybe if + // something vanishes during the search it will come up. + if (a && nota) return false; + + if (!a && !nota) + { + if (i == alarmSearchJunction) + { + // this is our time to decide differently, we went zero last time, go one. + a = 1; + alarmSearchJunction = lastJunction; + } + else if (i < alarmSearchJunction) + { + // take whatever we took last time, look in address + if (alarmSearchAddress[ibyte] & ibit) a = 1; + else + { + // Only 0s count as pending junctions, we've already exhasuted the 0 side of 1s + a = 0; + done = 0; + lastJunction = i; + } + } + else + { + // we are blazing new tree, take the 0 + a = 0; + alarmSearchJunction = i; + done = 0; + } + // OneWire search fix + // See: http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1238032295 + } + + if (a) alarmSearchAddress[ibyte] |= ibit; + else alarmSearchAddress[ibyte] &= ~ibit; + + _wire->write_bit(a); + } + + if (done) alarmSearchExhausted = 1; + for (i = 0; i < 8; i++) newAddr[i] = alarmSearchAddress[i]; + return true; +} + +// returns true if device address has an alarm condition +// TODO: can this be done with only TEMP_MSB REGISTER (faster) +// if ((char) scratchPad[TEMP_MSB] <= (char) scratchPad[LOW_ALARM_TEMP]) return true; +// if ((char) scratchPad[TEMP_MSB] >= (char) scratchPad[HIGH_ALARM_TEMP]) return true; +bool DallasTemperature::hasAlarm(uint8_t* deviceAddress) +{ + ScratchPad scratchPad; + if (isConnected(deviceAddress, scratchPad)) + { + float temp = calculateTemperature(deviceAddress, scratchPad); + + // check low alarm + if ((char)temp <= (char)scratchPad[LOW_ALARM_TEMP]) return true; + + // check high alarm + if ((char)temp >= (char)scratchPad[HIGH_ALARM_TEMP]) return true; + } + + // no alarm + return false; +} + +// returns true if any device is reporting an alarm condition on the bus +bool DallasTemperature::hasAlarm(void) +{ + DeviceAddress deviceAddress; + resetAlarmSearch(); + return alarmSearch(deviceAddress); +} + +// runs the alarm handler for all devices returned by alarmSearch() +void DallasTemperature::processAlarms(void) +{ + resetAlarmSearch(); + DeviceAddress alarmAddr; + + while (alarmSearch(alarmAddr)) + { + if (validAddress(alarmAddr)) + _AlarmHandler(alarmAddr); + } +} + +// sets the alarm handler +void DallasTemperature::setAlarmHandler(AlarmHandler *handler) +{ + _AlarmHandler = handler; +} + +// The default alarm handler +void DallasTemperature::defaultAlarmHandler(uint8_t* deviceAddress) +{ +} + +#endif + +// Convert float celsius to fahrenheit +float DallasTemperature::toFahrenheit(float celsius) +{ + return (celsius * 1.8) + 32; +} + +// Convert float fahrenheit to celsius +float DallasTemperature::toCelsius(float fahrenheit) +{ + return (fahrenheit - 32) / 1.8; +} + +#if REQUIRESNEW + +// MnetCS - Allocates memory for DallasTemperature. Allows us to instance a new object +void* DallasTemperature::operator new(unsigned int size) // Implicit NSS obj size +{ + void * p; // void pointer + p = malloc(size); // Allocate memory + memset((DallasTemperature*)p,0,size); // Initalise memory + + //!!! CANT EXPLICITLY CALL CONSTRUCTOR - workaround by using an init() methodR - workaround by using an init() method + return (DallasTemperature*) p; // Cast blank region to NSS pointer +} + +// MnetCS 2009 - Unallocates the memory used by this instance +void DallasTemperature::operator delete(void* p) +{ + DallasTemperature* pNss = (DallasTemperature*) p; // Cast to NSS pointer + pNss->~DallasTemperature(); // Destruct the object + + free(p); // Free the memory +} + +#endif diff --git a/ArduinoBridge/Libraries/DallasTemperature/DallasTemperature.h b/ArduinoBridge/Libraries/DallasTemperature/DallasTemperature.h new file mode 100644 index 0000000..ff8262f --- /dev/null +++ b/ArduinoBridge/Libraries/DallasTemperature/DallasTemperature.h @@ -0,0 +1,242 @@ +#ifndef DallasTemperature_h +#define DallasTemperature_h + +#define DALLASTEMPLIBVERSION "3.7.2" + +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. + +// set to true to include code for new and delete operators +#ifndef REQUIRESNEW +#define REQUIRESNEW false +#endif + +// set to true to include code implementing alarm search functions +#ifndef REQUIRESALARMS +#define REQUIRESALARMS true +#endif + +#include +#include + +// Model IDs +#define DS18S20MODEL 0x10 +#define DS18B20MODEL 0x28 +#define DS1822MODEL 0x22 + +// OneWire commands +#define STARTCONVO 0x44 // Tells device to take a temperature reading and put it on the scratchpad +#define COPYSCRATCH 0x48 // Copy EEPROM +#define READSCRATCH 0xBE // Read EEPROM +#define WRITESCRATCH 0x4E // Write to EEPROM +#define RECALLSCRATCH 0xB8 // Reload from last known +#define READPOWERSUPPLY 0xB4 // Determine if device needs parasite power +#define ALARMSEARCH 0xEC // Query bus for devices with an alarm condition + +// Scratchpad locations +#define TEMP_LSB 0 +#define TEMP_MSB 1 +#define HIGH_ALARM_TEMP 2 +#define LOW_ALARM_TEMP 3 +#define CONFIGURATION 4 +#define INTERNAL_BYTE 5 +#define COUNT_REMAIN 6 +#define COUNT_PER_C 7 +#define SCRATCHPAD_CRC 8 + +// Device resolution +#define TEMP_9_BIT 0x1F // 9 bit +#define TEMP_10_BIT 0x3F // 10 bit +#define TEMP_11_BIT 0x5F // 11 bit +#define TEMP_12_BIT 0x7F // 12 bit + +// Error Codes +#define DEVICE_DISCONNECTED -127 + +typedef uint8_t DeviceAddress[8]; + +class DallasTemperature +{ + public: + + DallasTemperature(OneWire*); + + // initalise bus + void begin(void); + + // returns the number of devices found on the bus + uint8_t getDeviceCount(void); + + // Is a conversion complete on the wire? + bool isConversionComplete(void); + + // returns true if address is valid + bool validAddress(uint8_t*); + + // finds an address at a given index on the bus + bool getAddress(uint8_t*, const uint8_t); + + // attempt to determine if the device at the given address is connected to the bus + bool isConnected(uint8_t*); + + // attempt to determine if the device at the given address is connected to the bus + // also allows for updating the read scratchpad + bool isConnected(uint8_t*, uint8_t*); + + // read device's scratchpad + void readScratchPad(uint8_t*, uint8_t*); + + // write device's scratchpad + void writeScratchPad(uint8_t*, const uint8_t*); + + // read device's power requirements + bool readPowerSupply(uint8_t*); + + // get global resolution + uint8_t getResolution(); + + // set global resolution to 9, 10, 11, or 12 bits + void setResolution(uint8_t); + + // returns the device resolution, 9-12 + uint8_t getResolution(uint8_t*); + + // set resolution of a device to 9, 10, 11, or 12 bits + bool setResolution(uint8_t*, uint8_t); + + // sets/gets the waitForConversion flag + void setWaitForConversion(bool); + bool getWaitForConversion(void); + + // sets/gets the checkForConversion flag + void setCheckForConversion(bool); + bool getCheckForConversion(void); + + // sends command for all devices on the bus to perform a temperature conversion + void requestTemperatures(void); + + // sends command for one device to perform a temperature conversion by address + bool requestTemperaturesByAddress(uint8_t*); + + // sends command for one device to perform a temperature conversion by index + bool requestTemperaturesByIndex(uint8_t); + + // returns temperature in degrees C + float getTempC(uint8_t*); + + // returns temperature in degrees F + float getTempF(uint8_t*); + + // Get temperature for device index (slow) + float getTempCByIndex(uint8_t); + + // Get temperature for device index (slow) + float getTempFByIndex(uint8_t); + + // returns true if the bus requires parasite power + bool isParasitePowerMode(void); + + bool isConversionAvailable(uint8_t*); + + #if REQUIRESALARMS + + typedef void AlarmHandler(uint8_t*); + + // sets the high alarm temperature for a device + // accepts a char. valid range is -55C - 125C + void setHighAlarmTemp(uint8_t*, const char); + + // sets the low alarm temperature for a device + // accepts a char. valid range is -55C - 125C + void setLowAlarmTemp(uint8_t*, const char); + + // returns a signed char with the current high alarm temperature for a device + // in the range -55C - 125C + char getHighAlarmTemp(uint8_t*); + + // returns a signed char with the current low alarm temperature for a device + // in the range -55C - 125C + char getLowAlarmTemp(uint8_t*); + + // resets internal variables used for the alarm search + void resetAlarmSearch(void); + + // search the wire for devices with active alarms + bool alarmSearch(uint8_t*); + + // returns true if ia specific device has an alarm + bool hasAlarm(uint8_t*); + + // returns true if any device is reporting an alarm on the bus + bool hasAlarm(void); + + // runs the alarm handler for all devices returned by alarmSearch() + void processAlarms(void); + + // sets the alarm handler + void setAlarmHandler(AlarmHandler *); + + // The default alarm handler + static void defaultAlarmHandler(uint8_t*); + + #endif + + // convert from celcius to farenheit + static float toFahrenheit(const float); + + // convert from farenheit to celsius + static float toCelsius(const float); + + #if REQUIRESNEW + + // initalize memory area + void* operator new (unsigned int); + + // delete memory reference + void operator delete(void*); + + #endif + + private: + typedef uint8_t ScratchPad[9]; + + // parasite power on or off + bool parasite; + + // used to determine the delay amount needed to allow for the + // temperature conversion to take place + uint8_t bitResolution; + + // used to requestTemperature with or without delay + bool waitForConversion; + + // used to requestTemperature to dynamically check if a conversion is complete + bool checkForConversion; + + // count of devices on the bus + uint8_t devices; + + // Take a pointer to one wire instance + OneWire* _wire; + + // reads scratchpad and returns the temperature in degrees C + float calculateTemperature(uint8_t*, uint8_t*); + + void blockTillConversionComplete(uint8_t*,uint8_t*); + + #if REQUIRESALARMS + + // required for alarmSearch + uint8_t alarmSearchAddress[8]; + char alarmSearchJunction; + uint8_t alarmSearchExhausted; + + // the alarm handler function pointer + AlarmHandler *_AlarmHandler; + + #endif + +}; +#endif diff --git a/ArduinoBridge/Libraries/DallasTemperature/README.TXT b/ArduinoBridge/Libraries/DallasTemperature/README.TXT new file mode 100644 index 0000000..764bdd7 --- /dev/null +++ b/ArduinoBridge/Libraries/DallasTemperature/README.TXT @@ -0,0 +1,53 @@ +Arduino Library for Dallas Temperature ICs +========================================== + +Usage +----- + +This library supports the following devices: + DS18B20 + DS18S20 - Please note there appears to be an issue with this series. + DS1822 + +You will need a pull-up resistor of about 5 KOhm between the 1-Wire data line +and your 5V power. If you are using the DS18B20, ground pins 1 and 3. The +centre pin is the data line '1-wire'. + +We have included a "REQUIRESNEW" and "REQUIRESALARMS" definition. If you +want to slim down the code feel free to use either of these by including +#define REQUIRESNEW or #define REQUIRESALARMS a the top of DallasTemperature.h + +Credits +------- + +The OneWire code has been derived from +http://www.arduino.cc/playground/Learning/OneWire. +Miles Burton originally developed this library. +Tim Newsome added support for multiple sensors on +the same bus. +Guil Barros [gfbarros@bappos.com] added getTempByAddress (v3.5) +Rob Tillaart [rob.tillaart@gmail.com] added async modus (v3.7.0) + + +Website +------- + +You can find the latest version of the library at +http://milesburton.com/index.php?title=Dallas_Temperature_Control_Library + +License +------- + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA diff --git a/ArduinoBridge/Libraries/DallasTemperature/change.txt b/ArduinoBridge/Libraries/DallasTemperature/change.txt new file mode 100644 index 0000000..42564ca --- /dev/null +++ b/ArduinoBridge/Libraries/DallasTemperature/change.txt @@ -0,0 +1,85 @@ + +This file contains the change history of the Dallas Temperature Control Library. + +VERSION 3.7.2 BETA +=================== +DATE: 6 DEC 2011 + +- Jordan Hochenbaum [jhochenbaum@gmail.com] updated library for compatibility with Arduino 1.0. + +VERSION 3.7.0 BETA +=================== +DATE: 11 JAN 2011 + +- Rob Tillaart [rob.tillaart@gmail.com] added async modus (v3.7.0) + The library is backwards compatible with version 3.6.0 + + MAJOR: async modus + ------------------ +- Added - private bool waitForConversion. +This boolean is default set to true in the Constructor to keep the library backwards compatible. If this flag is true calls to requestTemperatures(), requestTemperaturesByAddress() et al, will be blocking with the appropiate time specified (in datasheet) for the resolution used. If the flag is set to false, requestTemperatures() et al, will return immediately after the conversion command is send over the 1-wire interface. The programmer is responsible to wait long enough before reading the temperature values. This enables the application to do other things while waiting for a new reading, like calculations, update LCD, read/write other IO lines etc. See examples. + +- Added - void setWaitForConversion(bool); +To set the flag to true or false, depending on the modus needed. + +- Added - bool getWaitForConversion(void); +To get the current value of the flag. + +- Changed - void requestTemperatures(void); +Added a test (false == waitForConversion) to return immediately after the conversion command instead of waiting until the conversion is ready. + +- Changed - bool requestTemperaturesByAddress(uint8_t*); +Added a test (false == waitForConversion) to return immediately after the conversion command instead of waiting until the conversion is ready. + + + MINOR version number + -------------------- +- Added - #define DALLASTEMPLIBVERSION "3.7.0" +To indicate the version number in .h file + + + MINOR internal var bitResolution + ---------------------------- +- Changed - private int conversionDelay - is renamed to - private int bitResolution +As this variable holds the resolution. The delay for the conversion is derived from it. + +- Changed - uint8_t getResolution(uint8_t* deviceAddress); +If the device is not connected, it returns 0, otherwise it returns the resolution of the device. + +- Changed - bool setResolution(uint8_t* deviceAddress, uint8_t newResolution); +If the device is not connected, it returns FALSE (fail), otherwise it returns TRUE (succes). + +- Added - uint8_t getResolution(); +Returns bitResolution. + +- Added - void setResolution(uint8_t newResolution) +Sets the internal variable bitResolution, and all devices to this value + + + MINOR check connected state + ---------------------------- +- Changed - bool requestTemperaturesByIndex(deviceIndex) +Changed return type from void to bool. The function returns false if the device identified with [deviceIndex] is not found on the bus and true otherwise. + +- Changed - bool requestTemperaturesByAddress(deviceAddress) +Changed return type from void to bool. The function returns false if the device identified with [deviceAddress] is not found on the bus and true otherwise. +Added code to handle the DS18S20 which has a 9 bit resolution separately. +Changed code so the blocking delay matches the bitResolution set in the device with deviceAddress. + +- Changed - bool requestTemperaturesByIndex(uint8_t deviceIndex) +Changed return type from void to bool. The function returns false if the device identified with [deviceIndex] is not found on the bus and true otherwise. + + + +VERSION 3.6.0 +============== +DATE: 2010-10-10 + +- no detailed change history known except: + +- The OneWire code has been derived from +http://www.arduino.cc/playground/Learning/OneWire. +- Miles Burton originally developed this library. +- Tim Newsome added support for multiple sensors on +the same bus. +- Guil Barros [gfbarros@bappos.com] added getTempByAddress (v3.5) diff --git a/ArduinoBridge/Libraries/DallasTemperature/examples/Alarm/Alarm.pde b/ArduinoBridge/Libraries/DallasTemperature/examples/Alarm/Alarm.pde new file mode 100644 index 0000000..d9c6e6c --- /dev/null +++ b/ArduinoBridge/Libraries/DallasTemperature/examples/Alarm/Alarm.pde @@ -0,0 +1,162 @@ +#include +#include + +// Data wire is plugged into port 2 on the Arduino +#define ONE_WIRE_BUS 2 + +// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs) +OneWire oneWire(ONE_WIRE_BUS); + +// Pass our oneWire reference to Dallas Temperature. +DallasTemperature sensors(&oneWire); + +// arrays to hold device addresses +DeviceAddress insideThermometer, outsideThermometer; + +void setup(void) +{ + // start serial port + Serial.begin(9600); + Serial.println("Dallas Temperature IC Control Library Demo"); + + // Start up the library + sensors.begin(); + + // locate devices on the bus + Serial.print("Found "); + Serial.print(sensors.getDeviceCount(), DEC); + Serial.println(" devices."); + + // search for devices on the bus and assign based on an index. + if (!sensors.getAddress(insideThermometer, 0)) Serial.println("Unable to find address for Device 0"); + if (!sensors.getAddress(outsideThermometer, 1)) Serial.println("Unable to find address for Device 1"); + + // show the addresses we found on the bus + Serial.print("Device 0 Address: "); + printAddress(insideThermometer); + Serial.println(); + + Serial.print("Device 0 Alarms: "); + printAlarms(insideThermometer); + Serial.println(); + + Serial.print("Device 1 Address: "); + printAddress(outsideThermometer); + Serial.println(); + + Serial.print("Device 1 Alarms: "); + printAlarms(outsideThermometer); + Serial.println(); + + Serial.println("Setting alarm temps..."); + + // alarm when temp is higher than 30C + sensors.setHighAlarmTemp(insideThermometer, 30); + + // alarm when temp is lower than -10C + sensors.setLowAlarmTemp(insideThermometer, -10); + + // alarm when temp is higher than 31C + sensors.setHighAlarmTemp(outsideThermometer, 31); + + // alarn when temp is lower than 27C + sensors.setLowAlarmTemp(outsideThermometer, 27); + + Serial.print("New Device 0 Alarms: "); + printAlarms(insideThermometer); + Serial.println(); + + Serial.print("New Device 1 Alarms: "); + printAlarms(outsideThermometer); + Serial.println(); +} + +// function to print a device address +void printAddress(DeviceAddress deviceAddress) +{ + for (uint8_t i = 0; i < 8; i++) + { + if (deviceAddress[i] < 16) Serial.print("0"); + Serial.print(deviceAddress[i], HEX); + } +} + +// function to print the temperature for a device +void printTemperature(DeviceAddress deviceAddress) +{ + float tempC = sensors.getTempC(deviceAddress); + Serial.print("Temp C: "); + Serial.print(tempC); + Serial.print(" Temp F: "); + Serial.print(DallasTemperature::toFahrenheit(tempC)); +} + +void printAlarms(uint8_t deviceAddress[]) +{ + char temp; + temp = sensors.getHighAlarmTemp(deviceAddress); + Serial.print("High Alarm: "); + Serial.print(temp, DEC); + Serial.print("C/"); + Serial.print(DallasTemperature::toFahrenheit(temp)); + Serial.print("F | Low Alarm: "); + temp = sensors.getLowAlarmTemp(deviceAddress); + Serial.print(temp, DEC); + Serial.print("C/"); + Serial.print(DallasTemperature::toFahrenheit(temp)); + Serial.print("F"); +} + +// main function to print information about a device +void printData(DeviceAddress deviceAddress) +{ + Serial.print("Device Address: "); + printAddress(deviceAddress); + Serial.print(" "); + printTemperature(deviceAddress); + Serial.println(); +} + +void checkAlarm(DeviceAddress deviceAddress) +{ + if (sensors.hasAlarm(deviceAddress)) + { + Serial.print("ALARM: "); + printData(deviceAddress); + } +} + +void loop(void) +{ + // call sensors.requestTemperatures() to issue a global temperature + // request to all devices on the bus + Serial.print("Requesting temperatures..."); + sensors.requestTemperatures(); + Serial.println("DONE"); + + // Method 1: + // check each address individually for an alarm condition + checkAlarm(insideThermometer); + checkAlarm(outsideThermometer); +/* + // Alternate method: + // Search the bus and iterate through addresses of devices with alarms + + // space for the alarm device's address + DeviceAddress alarmAddr; + + Serial.println("Searching for alarms..."); + + // resetAlarmSearch() must be called before calling alarmSearch() + sensors.resetAlarmSearch(); + + // alarmSearch() returns 0 when there are no devices with alarms + while (sensors.alarmSearch(alarmAddr)) + { + Serial.print("ALARM: "); + printData(alarmAddr); + } +*/ + +} + diff --git a/ArduinoBridge/Libraries/DallasTemperature/examples/AlarmHandler/AlarmHandler.pde b/ArduinoBridge/Libraries/DallasTemperature/examples/AlarmHandler/AlarmHandler.pde new file mode 100644 index 0000000..4b72962 --- /dev/null +++ b/ArduinoBridge/Libraries/DallasTemperature/examples/AlarmHandler/AlarmHandler.pde @@ -0,0 +1,144 @@ +#include +#include + +// Data wire is plugged into port 2 on the Arduino +#define ONE_WIRE_BUS 2 + +// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs) +OneWire oneWire(ONE_WIRE_BUS); + +// Pass our oneWire reference to Dallas Temperature. +DallasTemperature sensors(&oneWire); + +// arrays to hold device addresses +DeviceAddress insideThermometer, outsideThermometer; + +// function that will be called when an alarm condition exists during DallasTemperatures::processAlarms(); +void newAlarmHandler(uint8_t* deviceAddress) +{ + Serial.println("Alarm Handler Start"); + printAlarmInfo(deviceAddress); + printTemp(deviceAddress); + Serial.println(); + Serial.println("Alarm Handler Finish"); +} + +void printCurrentTemp(DeviceAddress deviceAddress) +{ + printAddress(deviceAddress); + printTemp(deviceAddress); + Serial.println(); +} + +void printAddress(DeviceAddress deviceAddress) +{ + Serial.print("Address: "); + for (uint8_t i = 0; i < 8; i++) + { + if (deviceAddress[i] < 16) Serial.print("0"); + Serial.print(deviceAddress[i], HEX); + } + Serial.print(" "); +} + +void printTemp(DeviceAddress deviceAddress) +{ + float tempC = sensors.getTempC(deviceAddress); + if (tempC != DEVICE_DISCONNECTED) + { + Serial.print("Current Temp C: "); + Serial.print(tempC); + } + else Serial.print("DEVICE DISCONNECTED"); + Serial.print(" "); +} + +void printAlarmInfo(DeviceAddress deviceAddress) +{ + char temp; + printAddress(deviceAddress); + temp = sensors.getHighAlarmTemp(deviceAddress); + Serial.print("High Alarm: "); + Serial.print(temp, DEC); + Serial.print("C"); + Serial.print(" Low Alarm: "); + temp = sensors.getLowAlarmTemp(deviceAddress); + Serial.print(temp, DEC); + Serial.print("C"); + Serial.print(" "); +} + +void setup(void) +{ + // start serial port + Serial.begin(9600); + Serial.println("Dallas Temperature IC Control Library Demo"); + + // Start up the library + sensors.begin(); + + // locate devices on the bus + Serial.print("Found "); + Serial.print(sensors.getDeviceCount(), DEC); + Serial.println(" devices."); + + // search for devices on the bus and assign based on an index + if (!sensors.getAddress(insideThermometer, 0)) Serial.println("Unable to find address for Device 0"); + if (!sensors.getAddress(outsideThermometer, 1)) Serial.println("Unable to find address for Device 1"); + + Serial.print("Device insideThermometer "); + printAlarmInfo(insideThermometer); + Serial.println(); + + Serial.print("Device outsideThermometer "); + printAlarmInfo(outsideThermometer); + Serial.println(); + + // set alarm ranges + Serial.println("Setting alarm temps..."); + sensors.setHighAlarmTemp(insideThermometer, 26); + sensors.setLowAlarmTemp(insideThermometer, 22); + sensors.setHighAlarmTemp(outsideThermometer, 25); + sensors.setLowAlarmTemp(outsideThermometer, 21); + + Serial.print("New insideThermometer "); + printAlarmInfo(insideThermometer); + Serial.println(); + + Serial.print("New outsideThermometer "); + printAlarmInfo(outsideThermometer); + Serial.println(); + + // attach alarm handler + sensors.setAlarmHandler(&newAlarmHandler); + +} + +void loop(void) +{ + // ask the devices to measure the temperature + sensors.requestTemperatures(); + + // if an alarm condition exists as a result of the most recent + // requestTemperatures() request, it exists until the next time + // requestTemperatures() is called AND there isn't an alarm condition + // on the device + if (sensors.hasAlarm()) + { + Serial.println("Oh noes! There is at least one alarm on the bus."); + } + + // call alarm handler function defined by sensors.setAlarmHandler + // for each device reporting an alarm + sensors.processAlarms(); + + if (!sensors.hasAlarm()) + { + // just print out the current temperature + printCurrentTemp(insideThermometer); + printCurrentTemp(outsideThermometer); + } + + delay(1000); +} + diff --git a/ArduinoBridge/Libraries/DallasTemperature/examples/Multiple/Multiple.pde b/ArduinoBridge/Libraries/DallasTemperature/examples/Multiple/Multiple.pde new file mode 100644 index 0000000..37e7ba6 --- /dev/null +++ b/ArduinoBridge/Libraries/DallasTemperature/examples/Multiple/Multiple.pde @@ -0,0 +1,140 @@ +#include +#include + +// Data wire is plugged into port 2 on the Arduino +#define ONE_WIRE_BUS 2 +#define TEMPERATURE_PRECISION 9 + +// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs) +OneWire oneWire(ONE_WIRE_BUS); + +// Pass our oneWire reference to Dallas Temperature. +DallasTemperature sensors(&oneWire); + +// arrays to hold device addresses +DeviceAddress insideThermometer, outsideThermometer; + +void setup(void) +{ + // start serial port + Serial.begin(9600); + Serial.println("Dallas Temperature IC Control Library Demo"); + + // Start up the library + sensors.begin(); + + // locate devices on the bus + Serial.print("Locating devices..."); + Serial.print("Found "); + Serial.print(sensors.getDeviceCount(), DEC); + Serial.println(" devices."); + + // report parasite power requirements + Serial.print("Parasite power is: "); + if (sensors.isParasitePowerMode()) Serial.println("ON"); + else Serial.println("OFF"); + + // assign address manually. the addresses below will beed to be changed + // to valid device addresses on your bus. device address can be retrieved + // by using either oneWire.search(deviceAddress) or individually via + // sensors.getAddress(deviceAddress, index) + //insideThermometer = { 0x28, 0x1D, 0x39, 0x31, 0x2, 0x0, 0x0, 0xF0 }; + //outsideThermometer = { 0x28, 0x3F, 0x1C, 0x31, 0x2, 0x0, 0x0, 0x2 }; + + // search for devices on the bus and assign based on an index. ideally, + // you would do this to initially discover addresses on the bus and then + // use those addresses and manually assign them (see above) once you know + // the devices on your bus (and assuming they don't change). + // + // method 1: by index + if (!sensors.getAddress(insideThermometer, 0)) Serial.println("Unable to find address for Device 0"); + if (!sensors.getAddress(outsideThermometer, 1)) Serial.println("Unable to find address for Device 1"); + + // method 2: search() + // search() looks for the next device. Returns 1 if a new address has been + // returned. A zero might mean that the bus is shorted, there are no devices, + // or you have already retrieved all of them. It might be a good idea to + // check the CRC to make sure you didn't get garbage. The order is + // deterministic. You will always get the same devices in the same order + // + // Must be called before search() + //oneWire.reset_search(); + // assigns the first address found to insideThermometer + //if (!oneWire.search(insideThermometer)) Serial.println("Unable to find address for insideThermometer"); + // assigns the seconds address found to outsideThermometer + //if (!oneWire.search(outsideThermometer)) Serial.println("Unable to find address for outsideThermometer"); + + // show the addresses we found on the bus + Serial.print("Device 0 Address: "); + printAddress(insideThermometer); + Serial.println(); + + Serial.print("Device 1 Address: "); + printAddress(outsideThermometer); + Serial.println(); + + // set the resolution to 9 bit + sensors.setResolution(insideThermometer, TEMPERATURE_PRECISION); + sensors.setResolution(outsideThermometer, TEMPERATURE_PRECISION); + + Serial.print("Device 0 Resolution: "); + Serial.print(sensors.getResolution(insideThermometer), DEC); + Serial.println(); + + Serial.print("Device 1 Resolution: "); + Serial.print(sensors.getResolution(outsideThermometer), DEC); + Serial.println(); +} + +// function to print a device address +void printAddress(DeviceAddress deviceAddress) +{ + for (uint8_t i = 0; i < 8; i++) + { + // zero pad the address if necessary + if (deviceAddress[i] < 16) Serial.print("0"); + Serial.print(deviceAddress[i], HEX); + } +} + +// function to print the temperature for a device +void printTemperature(DeviceAddress deviceAddress) +{ + float tempC = sensors.getTempC(deviceAddress); + Serial.print("Temp C: "); + Serial.print(tempC); + Serial.print(" Temp F: "); + Serial.print(DallasTemperature::toFahrenheit(tempC)); +} + +// function to print a device's resolution +void printResolution(DeviceAddress deviceAddress) +{ + Serial.print("Resolution: "); + Serial.print(sensors.getResolution(deviceAddress)); + Serial.println(); +} + +// main function to print information about a device +void printData(DeviceAddress deviceAddress) +{ + Serial.print("Device Address: "); + printAddress(deviceAddress); + Serial.print(" "); + printTemperature(deviceAddress); + Serial.println(); +} + +void loop(void) +{ + // call sensors.requestTemperatures() to issue a global temperature + // request to all devices on the bus + Serial.print("Requesting temperatures..."); + sensors.requestTemperatures(); + Serial.println("DONE"); + + // print the device information + printData(insideThermometer); + printData(outsideThermometer); +} + diff --git a/ArduinoBridge/Libraries/DallasTemperature/examples/Simple/Simple.pde b/ArduinoBridge/Libraries/DallasTemperature/examples/Simple/Simple.pde new file mode 100644 index 0000000..5b2954d --- /dev/null +++ b/ArduinoBridge/Libraries/DallasTemperature/examples/Simple/Simple.pde @@ -0,0 +1,33 @@ +#include +#include + +// Data wire is plugged into port 2 on the Arduino +#define ONE_WIRE_BUS 2 + +// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs) +OneWire oneWire(ONE_WIRE_BUS); + +// Pass our oneWire reference to Dallas Temperature. +DallasTemperature sensors(&oneWire); + +void setup(void) +{ + // start serial port + Serial.begin(9600); + Serial.println("Dallas Temperature IC Control Library Demo"); + + // Start up the library + sensors.begin(); +} + +void loop(void) +{ + // call sensors.requestTemperatures() to issue a global temperature + // request to all devices on the bus + Serial.print("Requesting temperatures..."); + sensors.requestTemperatures(); // Send the command to get temperatures + Serial.println("DONE"); + + Serial.print("Temperature for the device 1 (index 0) is: "); + Serial.println(sensors.getTempCByIndex(0)); +} diff --git a/ArduinoBridge/Libraries/DallasTemperature/examples/Single/Single.pde b/ArduinoBridge/Libraries/DallasTemperature/examples/Single/Single.pde new file mode 100644 index 0000000..7336859 --- /dev/null +++ b/ArduinoBridge/Libraries/DallasTemperature/examples/Single/Single.pde @@ -0,0 +1,109 @@ +#include +#include + +// Data wire is plugged into port 2 on the Arduino +#define ONE_WIRE_BUS 2 + +// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs) +OneWire oneWire(ONE_WIRE_BUS); + +// Pass our oneWire reference to Dallas Temperature. +DallasTemperature sensors(&oneWire); + +// arrays to hold device address +DeviceAddress insideThermometer; + +void setup(void) +{ + // start serial port + Serial.begin(9600); + Serial.println("Dallas Temperature IC Control Library Demo"); + + // locate devices on the bus + Serial.print("Locating devices..."); + sensors.begin(); + Serial.print("Found "); + Serial.print(sensors.getDeviceCount(), DEC); + Serial.println(" devices."); + + // report parasite power requirements + Serial.print("Parasite power is: "); + if (sensors.isParasitePowerMode()) Serial.println("ON"); + else Serial.println("OFF"); + + // assign address manually. the addresses below will beed to be changed + // to valid device addresses on your bus. device address can be retrieved + // by using either oneWire.search(deviceAddress) or individually via + // sensors.getAddress(deviceAddress, index) + //insideThermometer = { 0x28, 0x1D, 0x39, 0x31, 0x2, 0x0, 0x0, 0xF0 }; + + // Method 1: + // search for devices on the bus and assign based on an index. ideally, + // you would do this to initially discover addresses on the bus and then + // use those addresses and manually assign them (see above) once you know + // the devices on your bus (and assuming they don't change). + if (!sensors.getAddress(insideThermometer, 0)) Serial.println("Unable to find address for Device 0"); + + // method 2: search() + // search() looks for the next device. Returns 1 if a new address has been + // returned. A zero might mean that the bus is shorted, there are no devices, + // or you have already retrieved all of them. It might be a good idea to + // check the CRC to make sure you didn't get garbage. The order is + // deterministic. You will always get the same devices in the same order + // + // Must be called before search() + //oneWire.reset_search(); + // assigns the first address found to insideThermometer + //if (!oneWire.search(insideThermometer)) Serial.println("Unable to find address for insideThermometer"); + + // show the addresses we found on the bus + Serial.print("Device 0 Address: "); + printAddress(insideThermometer); + Serial.println(); + + // set the resolution to 9 bit (Each Dallas/Maxim device is capable of several different resolutions) + sensors.setResolution(insideThermometer, 9); + + Serial.print("Device 0 Resolution: "); + Serial.print(sensors.getResolution(insideThermometer), DEC); + Serial.println(); +} + +// function to print the temperature for a device +void printTemperature(DeviceAddress deviceAddress) +{ + // method 1 - slower + //Serial.print("Temp C: "); + //Serial.print(sensors.getTempC(deviceAddress)); + //Serial.print(" Temp F: "); + //Serial.print(sensors.getTempF(deviceAddress)); // Makes a second call to getTempC and then converts to Fahrenheit + + // method 2 - faster + float tempC = sensors.getTempC(deviceAddress); + Serial.print("Temp C: "); + Serial.print(tempC); + Serial.print(" Temp F: "); + Serial.println(DallasTemperature::toFahrenheit(tempC)); // Converts tempC to Fahrenheit +} + +void loop(void) +{ + // call sensors.requestTemperatures() to issue a global temperature + // request to all devices on the bus + Serial.print("Requesting temperatures..."); + sensors.requestTemperatures(); // Send the command to get temperatures + Serial.println("DONE"); + + // It responds almost immediately. Let's print out the data + printTemperature(insideThermometer); // Use a simple function to print out the data +} + +// function to print a device address +void printAddress(DeviceAddress deviceAddress) +{ + for (uint8_t i = 0; i < 8; i++) + { + if (deviceAddress[i] < 16) Serial.print("0"); + Serial.print(deviceAddress[i], HEX); + } +} diff --git a/ArduinoBridge/Libraries/DallasTemperature/examples/Tester/Tester.pde b/ArduinoBridge/Libraries/DallasTemperature/examples/Tester/Tester.pde new file mode 100644 index 0000000..33ca9ba --- /dev/null +++ b/ArduinoBridge/Libraries/DallasTemperature/examples/Tester/Tester.pde @@ -0,0 +1,124 @@ +#include +#include + +// Data wire is plugged into port 2 on the Arduino +#define ONE_WIRE_BUS 2 +#define TEMPERATURE_PRECISION 9 + +// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs) +OneWire oneWire(ONE_WIRE_BUS); + +// Pass our oneWire reference to Dallas Temperature. +DallasTemperature sensors(&oneWire); + +int numberOfDevices; // Number of temperature devices found + +DeviceAddress tempDeviceAddress; // We'll use this variable to store a found device address + +void setup(void) +{ + // start serial port + Serial.begin(9600); + Serial.println("Dallas Temperature IC Control Library Demo"); + + // Start up the library + sensors.begin(); + + // Grab a count of devices on the wire + numberOfDevices = sensors.getDeviceCount(); + + // locate devices on the bus + Serial.print("Locating devices..."); + + Serial.print("Found "); + Serial.print(numberOfDevices, DEC); + Serial.println(" devices."); + + // report parasite power requirements + Serial.print("Parasite power is: "); + if (sensors.isParasitePowerMode()) Serial.println("ON"); + else Serial.println("OFF"); + + // Loop through each device, print out address + for(int i=0;i +#include + +// Data wire is plugged into port 2 on the Arduino +#define ONE_WIRE_BUS 2 + +// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs) +OneWire oneWire(ONE_WIRE_BUS); + +// Pass our oneWire reference to Dallas Temperature. +DallasTemperature sensors(&oneWire); + +void setup(void) +{ + // start serial port + Serial.begin(115200); + Serial.println("Dallas Temperature Control Library - Async Demo"); + Serial.println("\nDemo shows the difference in length of the call\n\n"); + + // Start up the library + sensors.begin(); +} + +void loop(void) +{ + // Request temperature conversion (traditional) + Serial.println("Before blocking requestForConversion"); + unsigned long start = millis(); + + sensors.requestTemperatures(); + + unsigned long stop = millis(); + Serial.println("After blocking requestForConversion"); + Serial.print("Time used: "); + Serial.println(stop - start); + + // get temperature + Serial.print("Temperature: "); + Serial.println(sensors.getTempCByIndex(0)); + Serial.println("\n"); + + // Request temperature conversion - non-blocking / async + Serial.println("Before NON-blocking/async requestForConversion"); + start = millis(); + sensors.setWaitForConversion(false); // makes it async + sensors.requestTemperatures(); + sensors.setWaitForConversion(true); + stop = millis(); + Serial.println("After NON-blocking/async requestForConversion"); + Serial.print("Time used: "); + Serial.println(stop - start); + + + // 9 bit resolution by default + // Note the programmer is responsible for the right delay + // we could do something usefull here instead of the delay + int resolution = 9; + delay(750/ (1 << (12-resolution))); + + // get temperature + Serial.print("Temperature: "); + Serial.println(sensors.getTempCByIndex(0)); + Serial.println("\n\n\n\n"); + + delay(5000); +} diff --git a/ArduinoBridge/Libraries/DallasTemperature/examples/WaitForConversion2/WaitForConversion2.pde b/ArduinoBridge/Libraries/DallasTemperature/examples/WaitForConversion2/WaitForConversion2.pde new file mode 100644 index 0000000..4322330 --- /dev/null +++ b/ArduinoBridge/Libraries/DallasTemperature/examples/WaitForConversion2/WaitForConversion2.pde @@ -0,0 +1,80 @@ +// +// Sample of using Async reading of Dallas Temperature Sensors +// +#include +#include + +// Data wire is plugged into port 2 on the Arduino +#define ONE_WIRE_BUS 2 + +// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs) +OneWire oneWire(ONE_WIRE_BUS); + +// Pass our oneWire reference to Dallas Temperature. +DallasTemperature sensors(&oneWire); + +DeviceAddress tempDeviceAddress; + +int resolution = 12; +unsigned long lastTempRequest = 0; +int delayInMillis = 0; +float temperature = 0.0; +int idle = 0; +// +// SETUP +// +void setup(void) +{ + Serial.begin(115200); + Serial.println("Dallas Temperature Control Library - Async Demo"); + Serial.print("Library Version: "); + Serial.println(DALLASTEMPLIBVERSION); + Serial.println("\n"); + + sensors.begin(); + sensors.getAddress(tempDeviceAddress, 0); + sensors.setResolution(tempDeviceAddress, resolution); + + sensors.setWaitForConversion(false); + sensors.requestTemperatures(); + delayInMillis = 750 / (1 << (12 - resolution)); + lastTempRequest = millis(); + + pinMode(13, OUTPUT); +} + +void loop(void) +{ + + if (millis() - lastTempRequest >= delayInMillis) // waited long enough?? + { + digitalWrite(13, LOW); + Serial.print(" Temperature: "); + temperature = sensors.getTempCByIndex(0); + Serial.println(temperature, resolution - 8); + Serial.print(" Resolution: "); + Serial.println(resolution); + Serial.print("Idle counter: "); + Serial.println(idle); + Serial.println(); + + idle = 0; + + // immediately after fetching the temperature we request a new sample + // in the async modus + // for the demo we let the resolution change to show differences + resolution++; + if (resolution > 12) resolution = 9; + + sensors.setResolution(tempDeviceAddress, resolution); + sensors.requestTemperatures(); + delayInMillis = 750 / (1 << (12 - resolution)); + lastTempRequest = millis(); + } + + digitalWrite(13, HIGH); + // we can do usefull things here + // for the demo we just count the idle time in millis + delay(1); + idle++; +} diff --git a/ArduinoBridge/Libraries/DallasTemperature/keywords.txt b/ArduinoBridge/Libraries/DallasTemperature/keywords.txt new file mode 100644 index 0000000..0212d44 --- /dev/null +++ b/ArduinoBridge/Libraries/DallasTemperature/keywords.txt @@ -0,0 +1,54 @@ +####################################### +# Syntax Coloring Map For Ultrasound +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### +DallasTemperature KEYWORD1 +OneWire KEYWORD1 +AlarmHandler KEYWORD1 +DeviceAddress KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +setResolution KEYWORD2 +getResolution KEYWORD2 +getTempC KEYWORD2 +toFahrenheit KEYWORD2 +getTempF KEYWORD2 +getTempCByIndex KEYWORD2 +getTempFByIndex KEYWORD2 +setWaitForConversion KEYWORD2 +getWaitForConversion KEYWORD2 +requestTemperatures KEYWORD2 +requestTemperaturesByAddress KEYWORD2 +requestTemperaturesByIndex KEYWORD2 +isParasitePowerMode KEYWORD2 +begin KEYWORD2 +getDeviceCount KEYWORD2 +getAddress KEYWORD2 +validAddress KEYWORD2 +isConnected KEYWORD2 +readScratchPad KEYWORD2 +writeScratchPad KEYWORD2 +readPowerSupply KEYWORD2 +setHighAlarmTemp KEYWORD2 +setLowAlarmTemp KEYWORD2 +getHighAlarmTemp KEYWORD2 +getLowAlarmTemp KEYWORD2 +resetAlarmSearch KEYWORD2 +alarmSearch KEYWORD2 +hasAlarm KEYWORD2 +toCelsius KEYWORD2 +processAlarmss KEYWORD2 +setAlarmHandlers KEYWORD2 +defaultAlarmHandler KEYWORD2 +calculateTemperature KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### + diff --git a/ArduinoBridge/Libraries/OneWire/OneWire.cpp b/ArduinoBridge/Libraries/OneWire/OneWire.cpp new file mode 100644 index 0000000..5c3561a --- /dev/null +++ b/ArduinoBridge/Libraries/OneWire/OneWire.cpp @@ -0,0 +1,527 @@ +/* +Copyright (c) 2007, Jim Studt (original old version - many contributors since) + +The latest version of this library may be found at: + http://www.pjrc.com/teensy/td_libs_OneWire.html + +Version 2.1: + Arduino 1.0 compatibility, Paul Stoffregen + Improve temperature example, Paul Stoffregen + DS250x_PROM example, Guillermo Lovato + PIC32 (chipKit) compatibility, Jason Dangel, dangel.jason AT gmail.com + Improvements from Glenn Trewitt: + - crc16() now works + - check_crc16() does all of calculation/checking work. + - Added read_bytes() and write_bytes(), to reduce tedious loops. + - Added ds2408 example. + Delete very old, out-of-date readme file (info is here) + +Version 2.0: Modifications by Paul Stoffregen, January 2010: +http://www.pjrc.com/teensy/td_libs_OneWire.html + Search fix from Robin James + http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1238032295/27#27 + Use direct optimized I/O in all cases + Disable interrupts during timing critical sections + (this solves many random communication errors) + Disable interrupts during read-modify-write I/O + Reduce RAM consumption by eliminating unnecessary + variables and trimming many to 8 bits + Optimize both crc8 - table version moved to flash + +Modified to work with larger numbers of devices - avoids loop. +Tested in Arduino 11 alpha with 12 sensors. +26 Sept 2008 -- Robin James +http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1238032295/27#27 + +Updated to work with arduino-0008 and to include skip() as of +2007/07/06. --RJL20 + +Modified to calculate the 8-bit CRC directly, avoiding the need for +the 256-byte lookup table to be loaded in RAM. Tested in arduino-0010 +-- Tom Pollard, Jan 23, 2008 + +Jim Studt's original library was modified by Josh Larios. + +Tom Pollard, pollard@alum.mit.edu, contributed around May 20, 2008 + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Much of the code was inspired by Derek Yerger's code, though I don't +think much of that remains. In any event that was.. + (copyleft) 2006 by Derek Yerger - Free to distribute freely. + +The CRC code was excerpted and inspired by the Dallas Semiconductor +sample code bearing this copyright. +//--------------------------------------------------------------------------- +// Copyright (C) 2000 Dallas Semiconductor Corporation, All Rights Reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL DALLAS SEMICONDUCTOR BE LIABLE FOR ANY CLAIM, DAMAGES +// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// Except as contained in this notice, the name of Dallas Semiconductor +// shall not be used except as stated in the Dallas Semiconductor +// Branding Policy. +//-------------------------------------------------------------------------- +*/ + +#include "OneWire.h" + + +OneWire::OneWire(uint8_t pin) +{ + pinMode(pin, INPUT); + bitmask = PIN_TO_BITMASK(pin); + baseReg = PIN_TO_BASEREG(pin); +#if ONEWIRE_SEARCH + reset_search(); +#endif +} + + +// Perform the onewire reset function. We will wait up to 250uS for +// the bus to come high, if it doesn't then it is broken or shorted +// and we return a 0; +// +// Returns 1 if a device asserted a presence pulse, 0 otherwise. +// +uint8_t OneWire::reset(void) +{ + IO_REG_TYPE mask = bitmask; + volatile IO_REG_TYPE *reg IO_REG_ASM = baseReg; + uint8_t r; + uint8_t retries = 125; + + noInterrupts(); + DIRECT_MODE_INPUT(reg, mask); + interrupts(); + // wait until the wire is high... just in case + do { + if (--retries == 0) return 0; + delayMicroseconds(2); + } while ( !DIRECT_READ(reg, mask)); + + noInterrupts(); + DIRECT_WRITE_LOW(reg, mask); + DIRECT_MODE_OUTPUT(reg, mask); // drive output low + interrupts(); + delayMicroseconds(500); + noInterrupts(); + DIRECT_MODE_INPUT(reg, mask); // allow it to float + delayMicroseconds(80); + r = !DIRECT_READ(reg, mask); + interrupts(); + delayMicroseconds(420); + return r; +} + +// +// Write a bit. Port and bit is used to cut lookup time and provide +// more certain timing. +// +void OneWire::write_bit(uint8_t v) +{ + IO_REG_TYPE mask=bitmask; + volatile IO_REG_TYPE *reg IO_REG_ASM = baseReg; + + if (v & 1) { + noInterrupts(); + DIRECT_WRITE_LOW(reg, mask); + DIRECT_MODE_OUTPUT(reg, mask); // drive output low + delayMicroseconds(10); + DIRECT_WRITE_HIGH(reg, mask); // drive output high + interrupts(); + delayMicroseconds(55); + } else { + noInterrupts(); + DIRECT_WRITE_LOW(reg, mask); + DIRECT_MODE_OUTPUT(reg, mask); // drive output low + delayMicroseconds(65); + DIRECT_WRITE_HIGH(reg, mask); // drive output high + interrupts(); + delayMicroseconds(5); + } +} + +// +// Read a bit. Port and bit is used to cut lookup time and provide +// more certain timing. +// +uint8_t OneWire::read_bit(void) +{ + IO_REG_TYPE mask=bitmask; + volatile IO_REG_TYPE *reg IO_REG_ASM = baseReg; + uint8_t r; + + noInterrupts(); + DIRECT_MODE_OUTPUT(reg, mask); + DIRECT_WRITE_LOW(reg, mask); + delayMicroseconds(3); + DIRECT_MODE_INPUT(reg, mask); // let pin float, pull up will raise + delayMicroseconds(10); + r = DIRECT_READ(reg, mask); + interrupts(); + delayMicroseconds(53); + return r; +} + +// +// Write a byte. The writing code uses the active drivers to raise the +// pin high, if you need power after the write (e.g. DS18S20 in +// parasite power mode) then set 'power' to 1, otherwise the pin will +// go tri-state at the end of the write to avoid heating in a short or +// other mishap. +// +void OneWire::write(uint8_t v, uint8_t power /* = 0 */) { + uint8_t bitMask; + + for (bitMask = 0x01; bitMask; bitMask <<= 1) { + OneWire::write_bit( (bitMask & v)?1:0); + } + if ( !power) { + noInterrupts(); + DIRECT_MODE_INPUT(baseReg, bitmask); + DIRECT_WRITE_LOW(baseReg, bitmask); + interrupts(); + } +} + +void OneWire::write_bytes(const uint8_t *buf, uint16_t count, bool power /* = 0 */) { + for (uint16_t i = 0 ; i < count ; i++) + write(buf[i]); + if (!power) { + noInterrupts(); + DIRECT_MODE_INPUT(baseReg, bitmask); + DIRECT_WRITE_LOW(baseReg, bitmask); + interrupts(); + } +} + +// +// Read a byte +// +uint8_t OneWire::read() { + uint8_t bitMask; + uint8_t r = 0; + + for (bitMask = 0x01; bitMask; bitMask <<= 1) { + if ( OneWire::read_bit()) r |= bitMask; + } + return r; +} + +void OneWire::read_bytes(uint8_t *buf, uint16_t count) { + for (uint16_t i = 0 ; i < count ; i++) + buf[i] = read(); +} + +// +// Do a ROM select +// +void OneWire::select( uint8_t rom[8]) +{ + int i; + + write(0x55); // Choose ROM + + for( i = 0; i < 8; i++) write(rom[i]); +} + +// +// Do a ROM skip +// +void OneWire::skip() +{ + write(0xCC); // Skip ROM +} + +void OneWire::depower() +{ + noInterrupts(); + DIRECT_MODE_INPUT(baseReg, bitmask); + interrupts(); +} + +#if ONEWIRE_SEARCH + +// +// You need to use this function to start a search again from the beginning. +// You do not need to do it for the first search, though you could. +// +void OneWire::reset_search() + { + // reset the search state + LastDiscrepancy = 0; + LastDeviceFlag = FALSE; + LastFamilyDiscrepancy = 0; + for(int i = 7; ; i--) + { + ROM_NO[i] = 0; + if ( i == 0) break; + } + } + +// +// Perform a search. If this function returns a '1' then it has +// enumerated the next device and you may retrieve the ROM from the +// OneWire::address variable. If there are no devices, no further +// devices, or something horrible happens in the middle of the +// enumeration then a 0 is returned. If a new device is found then +// its address is copied to newAddr. Use OneWire::reset_search() to +// start over. +// +// --- Replaced by the one from the Dallas Semiconductor web site --- +//-------------------------------------------------------------------------- +// Perform the 1-Wire Search Algorithm on the 1-Wire bus using the existing +// search state. +// Return TRUE : device found, ROM number in ROM_NO buffer +// FALSE : device not found, end of search +// +uint8_t OneWire::search(uint8_t *newAddr) +{ + uint8_t id_bit_number; + uint8_t last_zero, rom_byte_number, search_result; + uint8_t id_bit, cmp_id_bit; + + unsigned char rom_byte_mask, search_direction; + + // initialize for search + id_bit_number = 1; + last_zero = 0; + rom_byte_number = 0; + rom_byte_mask = 1; + search_result = 0; + + // if the last call was not the last one + if (!LastDeviceFlag) + { + // 1-Wire reset + if (!reset()) + { + // reset the search + LastDiscrepancy = 0; + LastDeviceFlag = FALSE; + LastFamilyDiscrepancy = 0; + return FALSE; + } + + // issue the search command + write(0xF0); + + // loop to do the search + do + { + // read a bit and its complement + id_bit = read_bit(); + cmp_id_bit = read_bit(); + + // check for no devices on 1-wire + if ((id_bit == 1) && (cmp_id_bit == 1)) + break; + else + { + // all devices coupled have 0 or 1 + if (id_bit != cmp_id_bit) + search_direction = id_bit; // bit write value for search + else + { + // if this discrepancy if before the Last Discrepancy + // on a previous next then pick the same as last time + if (id_bit_number < LastDiscrepancy) + search_direction = ((ROM_NO[rom_byte_number] & rom_byte_mask) > 0); + else + // if equal to last pick 1, if not then pick 0 + search_direction = (id_bit_number == LastDiscrepancy); + + // if 0 was picked then record its position in LastZero + if (search_direction == 0) + { + last_zero = id_bit_number; + + // check for Last discrepancy in family + if (last_zero < 9) + LastFamilyDiscrepancy = last_zero; + } + } + + // set or clear the bit in the ROM byte rom_byte_number + // with mask rom_byte_mask + if (search_direction == 1) + ROM_NO[rom_byte_number] |= rom_byte_mask; + else + ROM_NO[rom_byte_number] &= ~rom_byte_mask; + + // serial number search direction write bit + write_bit(search_direction); + + // increment the byte counter id_bit_number + // and shift the mask rom_byte_mask + id_bit_number++; + rom_byte_mask <<= 1; + + // if the mask is 0 then go to new SerialNum byte rom_byte_number and reset mask + if (rom_byte_mask == 0) + { + rom_byte_number++; + rom_byte_mask = 1; + } + } + } + while(rom_byte_number < 8); // loop until through all ROM bytes 0-7 + + // if the search was successful then + if (!(id_bit_number < 65)) + { + // search successful so set LastDiscrepancy,LastDeviceFlag,search_result + LastDiscrepancy = last_zero; + + // check for last device + if (LastDiscrepancy == 0) + LastDeviceFlag = TRUE; + + search_result = TRUE; + } + } + + // if no device found then reset counters so next 'search' will be like a first + if (!search_result || !ROM_NO[0]) + { + LastDiscrepancy = 0; + LastDeviceFlag = FALSE; + LastFamilyDiscrepancy = 0; + search_result = FALSE; + } + for (int i = 0; i < 8; i++) newAddr[i] = ROM_NO[i]; + return search_result; + } + +#endif + +#if ONEWIRE_CRC +// The 1-Wire CRC scheme is described in Maxim Application Note 27: +// "Understanding and Using Cyclic Redundancy Checks with Maxim iButton Products" +// + +#if ONEWIRE_CRC8_TABLE +// This table comes from Dallas sample code where it is freely reusable, +// though Copyright (C) 2000 Dallas Semiconductor Corporation +static const uint8_t PROGMEM dscrc_table[] = { + 0, 94,188,226, 97, 63,221,131,194,156,126, 32,163,253, 31, 65, + 157,195, 33,127,252,162, 64, 30, 95, 1,227,189, 62, 96,130,220, + 35,125,159,193, 66, 28,254,160,225,191, 93, 3,128,222, 60, 98, + 190,224, 2, 92,223,129, 99, 61,124, 34,192,158, 29, 67,161,255, + 70, 24,250,164, 39,121,155,197,132,218, 56,102,229,187, 89, 7, + 219,133,103, 57,186,228, 6, 88, 25, 71,165,251,120, 38,196,154, + 101, 59,217,135, 4, 90,184,230,167,249, 27, 69,198,152,122, 36, + 248,166, 68, 26,153,199, 37,123, 58,100,134,216, 91, 5,231,185, + 140,210, 48,110,237,179, 81, 15, 78, 16,242,172, 47,113,147,205, + 17, 79,173,243,112, 46,204,146,211,141,111, 49,178,236, 14, 80, + 175,241, 19, 77,206,144,114, 44,109, 51,209,143, 12, 82,176,238, + 50,108,142,208, 83, 13,239,177,240,174, 76, 18,145,207, 45,115, + 202,148,118, 40,171,245, 23, 73, 8, 86,180,234,105, 55,213,139, + 87, 9,235,181, 54,104,138,212,149,203, 41,119,244,170, 72, 22, + 233,183, 85, 11,136,214, 52,106, 43,117,151,201, 74, 20,246,168, + 116, 42,200,150, 21, 75,169,247,182,232, 10, 84,215,137,107, 53}; + +// +// Compute a Dallas Semiconductor 8 bit CRC. These show up in the ROM +// and the registers. (note: this might better be done without to +// table, it would probably be smaller and certainly fast enough +// compared to all those delayMicrosecond() calls. But I got +// confused, so I use this table from the examples.) +// +uint8_t OneWire::crc8( uint8_t *addr, uint8_t len) +{ + uint8_t crc = 0; + + while (len--) { + crc = pgm_read_byte(dscrc_table + (crc ^ *addr++)); + } + return crc; +} +#else +// +// Compute a Dallas Semiconductor 8 bit CRC directly. +// this is much slower, but much smaller, than the lookup table. +// +uint8_t OneWire::crc8( uint8_t *addr, uint8_t len) +{ + uint8_t crc = 0; + + while (len--) { + uint8_t inbyte = *addr++; + for (uint8_t i = 8; i; i--) { + uint8_t mix = (crc ^ inbyte) & 0x01; + crc >>= 1; + if (mix) crc ^= 0x8C; + inbyte >>= 1; + } + } + return crc; +} +#endif + +#if ONEWIRE_CRC16 +bool OneWire::check_crc16(uint8_t* input, uint16_t len, uint8_t* inverted_crc) +{ + uint16_t crc = ~crc16(input, len); + return (crc & 0xFF) == inverted_crc[0] && (crc >> 8) == inverted_crc[1]; +} + +uint16_t OneWire::crc16(uint8_t* input, uint16_t len) +{ + static const uint8_t oddparity[16] = + { 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 }; + uint16_t crc = 0; // Starting seed is zero. + + for (uint16_t i = 0 ; i < len ; i++) { + // Even though we're just copying a byte from the input, + // we'll be doing 16-bit computation with it. + uint16_t cdata = input[i]; + cdata = (cdata ^ (crc & 0xff)) & 0xff; + crc >>= 8; + + if (oddparity[cdata & 0x0F] ^ oddparity[cdata >> 4]) + crc ^= 0xC001; + + cdata <<= 6; + crc ^= cdata; + cdata <<= 1; + crc ^= cdata; + } + return crc; +} +#endif + +#endif diff --git a/ArduinoBridge/Libraries/OneWire/OneWire.h b/ArduinoBridge/Libraries/OneWire/OneWire.h new file mode 100644 index 0000000..2737b27 --- /dev/null +++ b/ArduinoBridge/Libraries/OneWire/OneWire.h @@ -0,0 +1,192 @@ +#ifndef OneWire_h +#define OneWire_h + +#include + +#if ARDUINO >= 100 +#include "Arduino.h" // for delayMicroseconds, digitalPinToBitMask, etc +#else +#include "WProgram.h" // for delayMicroseconds +#include "pins_arduino.h" // for digitalPinToBitMask, etc +#endif + +// You can exclude certain features from OneWire. In theory, this +// might save some space. In practice, the compiler automatically +// removes unused code (technically, the linker, using -fdata-sections +// and -ffunction-sections when compiling, and Wl,--gc-sections +// when linking), so most of these will not result in any code size +// reduction. Well, unless you try to use the missing features +// and redesign your program to not need them! ONEWIRE_CRC8_TABLE +// is the exception, because it selects a fast but large algorithm +// or a small but slow algorithm. + +// you can exclude onewire_search by defining that to 0 +#ifndef ONEWIRE_SEARCH +#define ONEWIRE_SEARCH 1 +#endif + +// You can exclude CRC checks altogether by defining this to 0 +#ifndef ONEWIRE_CRC +#define ONEWIRE_CRC 1 +#endif + +// Select the table-lookup method of computing the 8-bit CRC +// by setting this to 1. The lookup table enlarges code size by +// about 250 bytes. It does NOT consume RAM (but did in very +// old versions of OneWire). If you disable this, a slower +// but very compact algorithm is used. +#ifndef ONEWIRE_CRC8_TABLE +#define ONEWIRE_CRC8_TABLE 1 +#endif + +// You can allow 16-bit CRC checks by defining this to 1 +// (Note that ONEWIRE_CRC must also be 1.) +#ifndef ONEWIRE_CRC16 +#define ONEWIRE_CRC16 1 +#endif + +#define FALSE 0 +#define TRUE 1 + +// Platform specific I/O definitions + +#if defined(__AVR__) +#define PIN_TO_BASEREG(pin) (portInputRegister(digitalPinToPort(pin))) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define IO_REG_TYPE uint8_t +#define IO_REG_ASM asm("r30") +#define DIRECT_READ(base, mask) (((*(base)) & (mask)) ? 1 : 0) +#define DIRECT_MODE_INPUT(base, mask) ((*(base+1)) &= ~(mask)) +#define DIRECT_MODE_OUTPUT(base, mask) ((*(base+1)) |= (mask)) +#define DIRECT_WRITE_LOW(base, mask) ((*(base+2)) &= ~(mask)) +#define DIRECT_WRITE_HIGH(base, mask) ((*(base+2)) |= (mask)) + +#elif defined(__PIC32MX__) +#include // is this necessary? +#define PIN_TO_BASEREG(pin) (portModeRegister(digitalPinToPort(pin))) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define IO_REG_TYPE uint32_t +#define IO_REG_ASM +#define DIRECT_READ(base, mask) (((*(base+4)) & (mask)) ? 1 : 0) //PORTX + 0x10 +#define DIRECT_MODE_INPUT(base, mask) ((*(base+2)) = (mask)) //TRISXSET + 0x08 +#define DIRECT_MODE_OUTPUT(base, mask) ((*(base+1)) = (mask)) //TRISXCLR + 0x04 +#define DIRECT_WRITE_LOW(base, mask) ((*(base+8+1)) = (mask)) //LATXCLR + 0x24 +#define DIRECT_WRITE_HIGH(base, mask) ((*(base+8+2)) = (mask)) //LATXSET + 0x28 + +#else +#error "Please define I/O register types here" +#endif + + +class OneWire +{ + private: + IO_REG_TYPE bitmask; + volatile IO_REG_TYPE *baseReg; + +#if ONEWIRE_SEARCH + // global search state + unsigned char ROM_NO[8]; + uint8_t LastDiscrepancy; + uint8_t LastFamilyDiscrepancy; + uint8_t LastDeviceFlag; +#endif + + public: + OneWire( uint8_t pin); + + // Perform a 1-Wire reset cycle. Returns 1 if a device responds + // with a presence pulse. Returns 0 if there is no device or the + // bus is shorted or otherwise held low for more than 250uS + uint8_t reset(void); + + // Issue a 1-Wire rom select command, you do the reset first. + void select( uint8_t rom[8]); + + // Issue a 1-Wire rom skip command, to address all on bus. + void skip(void); + + // Write a byte. If 'power' is one then the wire is held high at + // the end for parasitically powered devices. You are responsible + // for eventually depowering it by calling depower() or doing + // another read or write. + void write(uint8_t v, uint8_t power = 0); + + void write_bytes(const uint8_t *buf, uint16_t count, bool power = 0); + + // Read a byte. + uint8_t read(void); + + void read_bytes(uint8_t *buf, uint16_t count); + + // Write a bit. The bus is always left powered at the end, see + // note in write() about that. + void write_bit(uint8_t v); + + // Read a bit. + uint8_t read_bit(void); + + // Stop forcing power onto the bus. You only need to do this if + // you used the 'power' flag to write() or used a write_bit() call + // and aren't about to do another read or write. You would rather + // not leave this powered if you don't have to, just in case + // someone shorts your bus. + void depower(void); + +#if ONEWIRE_SEARCH + // Clear the search state so that if will start from the beginning again. + void reset_search(); + + // Look for the next device. Returns 1 if a new address has been + // returned. A zero might mean that the bus is shorted, there are + // no devices, or you have already retrieved all of them. It + // might be a good idea to check the CRC to make sure you didn't + // get garbage. The order is deterministic. You will always get + // the same devices in the same order. + uint8_t search(uint8_t *newAddr); +#endif + +#if ONEWIRE_CRC + // Compute a Dallas Semiconductor 8 bit CRC, these are used in the + // ROM and scratchpad registers. + static uint8_t crc8( uint8_t *addr, uint8_t len); + +#if ONEWIRE_CRC16 + // Compute the 1-Wire CRC16 and compare it against the received CRC. + // Example usage (reading a DS2408): + // // Put everything in a buffer so we can compute the CRC easily. + // uint8_t buf[13]; + // buf[0] = 0xF0; // Read PIO Registers + // buf[1] = 0x88; // LSB address + // buf[2] = 0x00; // MSB address + // WriteBytes(net, buf, 3); // Write 3 cmd bytes + // ReadBytes(net, buf+3, 10); // Read 6 data bytes, 2 0xFF, 2 CRC16 + // if (!CheckCRC16(buf, 11, &buf[11])) { + // // Handle error. + // } + // + // @param input - Array of bytes to checksum. + // @param len - How many bytes to use. + // @param inverted_crc - The two CRC16 bytes in the received data. + // This should just point into the received data, + // *not* at a 16-bit integer. + // @return True, iff the CRC matches. + static bool check_crc16(uint8_t* input, uint16_t len, uint8_t* inverted_crc); + + // Compute a Dallas Semiconductor 16 bit CRC. This is required to check + // the integrity of data received from many 1-Wire devices. Note that the + // CRC computed here is *not* what you'll get from the 1-Wire network, + // for two reasons: + // 1) The CRC is transmitted bitwise inverted. + // 2) Depending on the endian-ness of your processor, the binary + // representation of the two-byte return value may have a different + // byte order than the two bytes you get from 1-Wire. + // @param input - Array of bytes to checksum. + // @param len - How many bytes to use. + // @return The CRC16, as defined by Dallas Semiconductor. + static uint16_t crc16(uint8_t* input, uint16_t len); +#endif +#endif +}; + +#endif diff --git a/ArduinoBridge/Libraries/OneWire/examples/DS18x20_Temperature/DS18x20_Temperature.pde b/ArduinoBridge/Libraries/OneWire/examples/DS18x20_Temperature/DS18x20_Temperature.pde new file mode 100644 index 0000000..1d632ca --- /dev/null +++ b/ArduinoBridge/Libraries/OneWire/examples/DS18x20_Temperature/DS18x20_Temperature.pde @@ -0,0 +1,109 @@ +#include + +// OneWire DS18S20, DS18B20, DS1822 Temperature Example +// +// http://www.pjrc.com/teensy/td_libs_OneWire.html +// +// The DallasTemperature library can do all this work for you! +// http://milesburton.com/Dallas_Temperature_Control_Library + +OneWire ds(10); // on pin 10 + +void setup(void) { + Serial.begin(9600); +} + +void loop(void) { + byte i; + byte present = 0; + byte type_s; + byte data[12]; + byte addr[8]; + float celsius, fahrenheit; + + if ( !ds.search(addr)) { + Serial.println("No more addresses."); + Serial.println(); + ds.reset_search(); + delay(250); + return; + } + + Serial.print("ROM ="); + for( i = 0; i < 8; i++) { + Serial.write(' '); + Serial.print(addr[i], HEX); + } + + if (OneWire::crc8(addr, 7) != addr[7]) { + Serial.println("CRC is not valid!"); + return; + } + Serial.println(); + + // the first ROM byte indicates which chip + switch (addr[0]) { + case 0x10: + Serial.println(" Chip = DS18S20"); // or old DS1820 + type_s = 1; + break; + case 0x28: + Serial.println(" Chip = DS18B20"); + type_s = 0; + break; + case 0x22: + Serial.println(" Chip = DS1822"); + type_s = 0; + break; + default: + Serial.println("Device is not a DS18x20 family device."); + return; + } + + ds.reset(); + ds.select(addr); + ds.write(0x44,1); // start conversion, with parasite power on at the end + + delay(1000); // maybe 750ms is enough, maybe not + // we might do a ds.depower() here, but the reset will take care of it. + + present = ds.reset(); + ds.select(addr); + ds.write(0xBE); // Read Scratchpad + + Serial.print(" Data = "); + Serial.print(present,HEX); + Serial.print(" "); + for ( i = 0; i < 9; i++) { // we need 9 bytes + data[i] = ds.read(); + Serial.print(data[i], HEX); + Serial.print(" "); + } + Serial.print(" CRC="); + Serial.print(OneWire::crc8(data, 8), HEX); + Serial.println(); + + // convert the data to actual temperature + + unsigned int raw = (data[1] << 8) | data[0]; + if (type_s) { + raw = raw << 3; // 9 bit resolution default + if (data[7] == 0x10) { + // count remain gives full 12 bit resolution + raw = (raw & 0xFFF0) + 12 - data[6]; + } + } else { + byte cfg = (data[4] & 0x60); + if (cfg == 0x00) raw = raw << 3; // 9 bit resolution, 93.75 ms + else if (cfg == 0x20) raw = raw << 2; // 10 bit res, 187.5 ms + else if (cfg == 0x40) raw = raw << 1; // 11 bit res, 375 ms + // default is 12 bit resolution, 750 ms conversion time + } + celsius = (float)raw / 16.0; + fahrenheit = celsius * 1.8 + 32.0; + Serial.print(" Temperature = "); + Serial.print(celsius); + Serial.print(" Celsius, "); + Serial.print(fahrenheit); + Serial.println(" Fahrenheit"); +} diff --git a/ArduinoBridge/Libraries/OneWire/examples/DS2408_Switch/DS2408_Switch.pde b/ArduinoBridge/Libraries/OneWire/examples/DS2408_Switch/DS2408_Switch.pde new file mode 100644 index 0000000..d171f9b --- /dev/null +++ b/ArduinoBridge/Libraries/OneWire/examples/DS2408_Switch/DS2408_Switch.pde @@ -0,0 +1,77 @@ +#include + +/* + * DS2408 8-Channel Addressable Switch + * + * Writte by Glenn Trewitt, glenn at trewitt dot org + * + * Some notes about the DS2408: + * - Unlike most input/output ports, the DS2408 doesn't have mode bits to + * set whether the pins are input or output. If you issue a read command, + * they're inputs. If you write to them, they're outputs. + * - For reading from a switch, you should use 10K pull-up resisters. + */ + +void PrintBytes(uint8_t* addr, uint8_t count, bool newline=0) { + for (uint8_t i = 0; i < count; i++) { + Serial.print(addr[i]>>4, HEX); + Serial.print(addr[i]&0x0f, HEX); + } + if (newline) + Serial.println(); +} + +void ReadAndReport(OneWire* net, uint8_t* addr) { + Serial.print(" Reading DS2408 "); + PrintBytes(addr, 8); + Serial.println(); + + uint8_t buf[13]; // Put everything in the buffer so we can compute CRC easily. + buf[0] = 0xF0; // Read PIO Registers + buf[1] = 0x88; // LSB address + buf[2] = 0x00; // MSB address + net->write_bytes(buf, 3); + net->read_bytes(buf+3, 10); // 3 cmd bytes, 6 data bytes, 2 0xFF, 2 CRC16 + net->reset(); + + if (!OneWire::check_crc16(buf, 11, &buf[11])) { + Serial.print("CRC failure in DS2408 at "); + PrintBytes(addr, 8, true); + return; + } + Serial.print(" DS2408 data = "); + // First 3 bytes contain command, register address. + Serial.println(buf[3], BIN); +} + +OneWire net(10); // on pin 10 + +void setup(void) { + Serial.begin(9600); +} + +void loop(void) { + byte i; + byte present = 0; + byte addr[8]; + + if (!net.search(addr)) { + Serial.print("No more addresses.\n"); + net.reset_search(); + delay(1000); + return; + } + + if (OneWire::crc8(addr, 7) != addr[7]) { + Serial.print("CRC is not valid!\n"); + return; + } + + if (addr[0] != 0x29) { + PrintBytes(addr, 8); + Serial.print(" is not a DS2408.\n"); + return; + } + + ReadAndReport(&net, addr); +} diff --git a/ArduinoBridge/Libraries/OneWire/examples/DS250x_PROM/DS250x_PROM.pde b/ArduinoBridge/Libraries/OneWire/examples/DS250x_PROM/DS250x_PROM.pde new file mode 100644 index 0000000..baa51c8 --- /dev/null +++ b/ArduinoBridge/Libraries/OneWire/examples/DS250x_PROM/DS250x_PROM.pde @@ -0,0 +1,90 @@ +/* +DS250x add-only programmable memory reader w/SKIP ROM. + + The DS250x is a 512/1024bit add-only PROM(you can add data but cannot change the old one) that's used mainly for device identification purposes + like serial number, mfgr data, unique identifiers, etc. It uses the Maxim 1-wire bus. + + This sketch will use the SKIP ROM function that skips the 1-Wire search phase since we only have one device connected in the bus on digital pin 6. + If more than one device is connected to the bus, it will fail. + Sketch will not verify if device connected is from the DS250x family since the skip rom function effectively skips the family-id byte readout. + thus it is possible to run this sketch with any Maxim OneWire device in which case the command CRC will most likely fail. + Sketch will only read the first page of memory(32bits) starting from the lower address(0000h), if more than 1 device is present, then use the sketch with search functions. + Remember to put a 4.7K pullup resistor between pin 6 and +Vcc + + To change the range or ammount of data to read, simply change the data array size, LSB/MSB addresses and for loop iterations + + This example code is in the public domain and is provided AS-IS. + + Built with Arduino 0022 and PJRC OneWire 2.0 library http://www.pjrc.com/teensy/td_libs_OneWire.html + + created by Guillermo Lovato + march/2011 + + */ + +#include +OneWire ds(6); // OneWire bus on digital pin 6 +void setup() { + Serial.begin (9600); +} + +void loop() { + byte i; // This is for the for loops + boolean present; // device present var + byte data[32]; // container for the data from device + byte leemem[3] = { // array with the commands to initiate a read, DS250x devices expect 3 bytes to start a read: command,LSB&MSB adresses + 0xF0 , 0x00 , 0x00 }; // 0xF0 is the Read Data command, followed by 00h 00h as starting address(the beginning, 0000h) + byte ccrc; // Variable to store the command CRC + byte ccrc_calc; + + present = ds.reset(); // OneWire bus reset, always needed to start operation on the bus, returns a 1/TRUE if there's a device present. + ds.skip(); // Skip ROM search + + if (present == TRUE){ // We only try to read the data if there's a device present + Serial.println("DS250x device present"); + ds.write(leemem[0],1); // Read data command, leave ghost power on + ds.write(leemem[1],1); // LSB starting address, leave ghost power on + ds.write(leemem[2],1); // MSB starting address, leave ghost power on + + ccrc = ds.read(); // DS250x generates a CRC for the command we sent, we assign a read slot and store it's value + ccrc_calc = OneWire::crc8(leemem, 3); // We calculate the CRC of the commands we sent using the library function and store it + + if ( ccrc_calc != ccrc) { // Then we compare it to the value the ds250x calculated, if it fails, we print debug messages and abort + Serial.println("Invalid command CRC!"); + Serial.print("Calculated CRC:"); + Serial.println(ccrc_calc,HEX); // HEX makes it easier to observe and compare + Serial.print("DS250x readback CRC:"); + Serial.println(ccrc,HEX); + return; // Since CRC failed, we abort the rest of the loop and start over + } + Serial.println("Data is: "); // For the printout of the data + for ( i = 0; i < 32; i++) { // Now it's time to read the PROM data itself, each page is 32 bytes so we need 32 read commands + data[i] = ds.read(); // we store each read byte to a different position in the data array + Serial.print(data[i]); // printout in ASCII + Serial.print(" "); // blank space + } + Serial.println(); + delay(5000); // Delay so we don't saturate the serial output + } + else { // Nothing is connected in the bus + Serial.println("Nothing connected"); + delay(3000); + } +} + + + + + + + + + + + + + + + + + diff --git a/ArduinoBridge/Libraries/OneWire/keywords.txt b/ArduinoBridge/Libraries/OneWire/keywords.txt new file mode 100644 index 0000000..bee5d90 --- /dev/null +++ b/ArduinoBridge/Libraries/OneWire/keywords.txt @@ -0,0 +1,38 @@ +####################################### +# Syntax Coloring Map For OneWire +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +OneWire KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +reset KEYWORD2 +write_bit KEYWORD2 +read_bit KEYWORD2 +write KEYWORD2 +write_bytes KEYWORD2 +read KEYWORD2 +read_bytes KEYWORD2 +select KEYWORD2 +skip KEYWORD2 +depower KEYWORD2 +reset_search KEYWORD2 +search KEYWORD2 +crc8 KEYWORD2 +crc16 KEYWORD2 +check_crc16 KEYWORD2 + +####################################### +# Instances (KEYWORD2) +####################################### + + +####################################### +# Constants (LITERAL1) +####################################### diff --git a/ArduinoBridge/Libraries/SerialCommand/SerialCommand.cpp b/ArduinoBridge/Libraries/SerialCommand/SerialCommand.cpp new file mode 100644 index 0000000..bbea5ba --- /dev/null +++ b/ArduinoBridge/Libraries/SerialCommand/SerialCommand.cpp @@ -0,0 +1,144 @@ +/** + * SerialCommand - A Wiring/Arduino library to tokenize and parse commands + * received over a serial port. + * + * Copyright (C) 2012 Stefan Rado + * Copyright (C) 2011 Steven Cogswell + * http://husks.wordpress.com + * + * Version 20120522 + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library. If not, see . + */ +#include "SerialCommand.h" + +/** + * Constructor makes sure some things are set. + */ +SerialCommand::SerialCommand() + : commandList(NULL), + commandCount(0), + defaultHandler(NULL), + term('\n'), // default terminator for commands, newline character + last(NULL) +{ + strcpy(delim, " "); // strtok_r needs a null-terminated string + clearBuffer(); +} + +/** + * Adds a "command" and a handler function to the list of available commands. + * This is used for matching a found token in the buffer, and gives the pointer + * to the handler function to deal with it. + */ +void SerialCommand::addCommand(const char *command, void (*function)()) { + #ifdef SERIALCOMMAND_DEBUG + Serial.print("Adding command ("); + Serial.print(commandCount); + Serial.print("): "); + Serial.println(command); + #endif + + commandList = (SerialCommandCallback *) realloc(commandList, (commandCount + 1) * sizeof(SerialCommandCallback)); + strncpy(commandList[commandCount].command, command, SERIALCOMMAND_MAXCOMMANDLENGTH); + commandList[commandCount].function = function; + commandCount++; +} + +/** + * This sets up a handler to be called in the event that the receveived command string + * isn't in the list of commands. + */ +void SerialCommand::setDefaultHandler(void (*function)(const char *)) { + defaultHandler = function; +} + + +/** + * This checks the Serial stream for characters, and assembles them into a buffer. + * When the terminator character (default '\n') is seen, it starts parsing the + * buffer for a prefix command, and calls handlers setup by addCommand() member + */ +void SerialCommand::readSerial() { + while (Serial.available() > 0) { + char inChar = Serial.read(); // Read single available character, there may be more waiting + #ifdef SERIALCOMMAND_DEBUG + Serial.print(inChar); // Echo back to serial stream + #endif + + if (inChar == term) { // Check for the terminator (default '\r') meaning end of command + #ifdef SERIALCOMMAND_DEBUG + Serial.print("Received: "); + Serial.println(buffer); + #endif + + char *command = strtok_r(buffer, delim, &last); // Search for command at start of buffer + if (command != NULL) { + boolean matched = false; + for (int i = 0; i < commandCount; i++) { + #ifdef SERIALCOMMAND_DEBUG + Serial.print("Comparing ["); + Serial.print(command); + Serial.print("] to ["); + Serial.print(commandList[i].command); + Serial.println("]"); + #endif + + // Compare the found command against the list of known commands for a match + if (strncmp(command, commandList[i].command, SERIALCOMMAND_MAXCOMMANDLENGTH) == 0) { + #ifdef SERIALCOMMAND_DEBUG + Serial.print("Matched Command: "); + Serial.println(command); + #endif + + // Execute the stored handler function for the command + (*commandList[i].function)(); + matched = true; + break; + } + } + if (!matched && (defaultHandler != NULL)) { + (*defaultHandler)(command); + } + } + clearBuffer(); + } + else if (isprint(inChar)) { // Only printable characters into the buffer + if (bufPos < SERIALCOMMAND_BUFFER) { + buffer[bufPos++] = inChar; // Put character into buffer + buffer[bufPos] = '\0'; // Null terminate + } else { + #ifdef SERIALCOMMAND_DEBUG + Serial.println("Line buffer is full - increase SERIALCOMMAND_BUFFER"); + #endif + } + } + } +} + +/* + * Clear the input buffer. + */ +void SerialCommand::clearBuffer() { + buffer[0] = '\0'; + bufPos = 0; +} + +/** + * Retrieve the next token ("word" or "argument") from the command buffer. + * Returns NULL if no more tokens exist. + */ +char *SerialCommand::next() { + return strtok_r(NULL, delim, &last); +} diff --git a/ArduinoBridge/Libraries/SerialCommand/SerialCommand.h b/ArduinoBridge/Libraries/SerialCommand/SerialCommand.h new file mode 100644 index 0000000..e00dd29 --- /dev/null +++ b/ArduinoBridge/Libraries/SerialCommand/SerialCommand.h @@ -0,0 +1,75 @@ +/** + * SerialCommand - A Wiring/Arduino library to tokenize and parse commands + * received over a serial port. + * + * Copyright (C) 2012 Stefan Rado + * Copyright (C) 2011 Steven Cogswell + * http://husks.wordpress.com + * + * Version 20120522 + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library. If not, see . + */ +#ifndef SerialCommand_h +#define SerialCommand_h + +#if defined(WIRING) && WIRING >= 100 + #include +#elif defined(ARDUINO) && ARDUINO >= 100 + #include +#else + #include +#endif +#include + +// Size of the input buffer in bytes (maximum length of one command plus arguments) +#define SERIALCOMMAND_BUFFER 32 +// Maximum length of a command excluding the terminating null +#define SERIALCOMMAND_MAXCOMMANDLENGTH 8 + +// Uncomment the next line to run the library in debug mode (verbose messages) +//#define SERIALCOMMAND_DEBUG + + +class SerialCommand { + public: + SerialCommand(); // Constructor + void addCommand(const char *command, void(*function)()); // Add a command to the processing dictionary. + void setDefaultHandler(void (*function)(const char *)); // A handler to call when no valid command received. + + void readSerial(); // Main entry point. + void clearBuffer(); // Clears the input buffer. + char *next(); // Returns pointer to next token found in command buffer (for getting arguments to commands). + + private: + // Command/handler dictionary + struct SerialCommandCallback { + char command[SERIALCOMMAND_MAXCOMMANDLENGTH + 1]; + void (*function)(); + }; // Data structure to hold Command/Handler function key-value pairs + SerialCommandCallback *commandList; // Actual definition for command/handler array + byte commandCount; + + // Pointer to the default handler function + void (*defaultHandler)(const char *); + + char delim[2]; // null-terminated list of character to be used as delimeters for tokenizing (default " ") + char term; // Character that signals end of command (default '\n') + + char buffer[SERIALCOMMAND_BUFFER + 1]; // Buffer of stored characters while waiting for terminator character + byte bufPos; // Current position in the buffer + char *last; // State variable used by strtok_r during processing +}; + +#endif //SerialCommand_h diff --git a/ArduinoBridge/Libraries/SerialCommand/examples/SerialCommandExample/SerialCommandExample.pde b/ArduinoBridge/Libraries/SerialCommand/examples/SerialCommandExample/SerialCommandExample.pde new file mode 100644 index 0000000..8d0bf4a --- /dev/null +++ b/ArduinoBridge/Libraries/SerialCommand/examples/SerialCommandExample/SerialCommandExample.pde @@ -0,0 +1,83 @@ +// Demo Code for SerialCommand Library +// Steven Cogswell +// May 2011 + +#include + +#define arduinoLED 13 // Arduino LED on board + +SerialCommand sCmd; // The demo SerialCommand object + +void setup() { + pinMode(arduinoLED, OUTPUT); // Configure the onboard LED for output + digitalWrite(arduinoLED, LOW); // default to LED off + + Serial.begin(9600); + + // Setup callbacks for SerialCommand commands + sCmd.addCommand("ON", LED_on); // Turns LED on + sCmd.addCommand("OFF", LED_off); // Turns LED off + sCmd.addCommand("HELLO", sayHello); // Echos the string argument back + sCmd.addCommand("P", processCommand); // Converts two arguments to integers and echos them back + sCmd.setDefaultHandler(unrecognized); // Handler for command that isn't matched (says "What?") + Serial.println("Ready"); +} + +void loop() { + sCmd.readSerial(); // We don't do much, just process serial commands +} + + +void LED_on() { + Serial.println("LED on"); + digitalWrite(arduinoLED, HIGH); +} + +void LED_off() { + Serial.println("LED off"); + digitalWrite(arduinoLED, LOW); +} + +void sayHello() { + char *arg; + arg = sCmd.next(); // Get the next argument from the SerialCommand object buffer + if (arg != NULL) { // As long as it existed, take it + Serial.print("Hello "); + Serial.println(arg); + } + else { + Serial.println("Hello, whoever you are"); + } +} + + +void processCommand() { + int aNumber; + char *arg; + + Serial.println("We're in processCommand"); + arg = sCmd.next(); + if (arg != NULL) { + aNumber = atoi(arg); // Converts a char string to an integer + Serial.print("First argument was: "); + Serial.println(aNumber); + } + else { + Serial.println("No arguments"); + } + + arg = sCmd.next(); + if (arg != NULL) { + aNumber = atol(arg); + Serial.print("Second argument was: "); + Serial.println(aNumber); + } + else { + Serial.println("No second argument"); + } +} + +// This gets set as the default handler, and gets called when no other command matches. +void unrecognized(const char *command) { + Serial.println("What?"); +} diff --git a/ArduinoBridge/Libraries/SerialCommand/keywords.txt b/ArduinoBridge/Libraries/SerialCommand/keywords.txt new file mode 100644 index 0000000..45ada90 --- /dev/null +++ b/ArduinoBridge/Libraries/SerialCommand/keywords.txt @@ -0,0 +1,23 @@ +####################################### +# Datatypes (KEYWORD1) +####################################### + +SerialCommand KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +addCommand KEYWORD2 +setDefaultHandler KEYWORD2 +readSerial KEYWORD2 +clearBuffer KEYWORD2 +next KEYWORD2 + +####################################### +# Instances (KEYWORD2) +####################################### + +####################################### +# Constants (LITERAL1) +####################################### diff --git a/ArduinoBridge/Libraries/SerialCommand/readme.md b/ArduinoBridge/Libraries/SerialCommand/readme.md new file mode 100644 index 0000000..c439eca --- /dev/null +++ b/ArduinoBridge/Libraries/SerialCommand/readme.md @@ -0,0 +1,7 @@ +SerialCommand +============= +A Wiring/Arduino library to tokenize and parse commands received over a serial port. + +The original version of this library was written by [Steven Cogswell](http://husks.wordpress.com) (published May 23, 2011 in his blog post ["A Minimal Arduino Library for Processing Serial Commands"](http://husks.wordpress.com/2011/05/23/a-minimal-arduino-library-for-processing-serial-commands/)). + +This is a heavily modified version with smaller footprint and a cleaned up code by Stefan Rado. diff --git a/Controller/Controller.csproj b/Controller/Controller.csproj index 2f70661..7e8b83e 100644 --- a/Controller/Controller.csproj +++ b/Controller/Controller.csproj @@ -1,84 +1,91 @@ - - - - Controller - Exe - Controller - {b69e3092-b931-443c-abe7-7e7b65f2a37f};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 9.0.21022 - 2.0 - {117848BD-213C-4AE4-8F58-D27F14DAA534} - v4.2 - $(MSBuildExtensionsPath32)\Microsoft\.NET Micro Framework\ - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - true - bin\Emulator\ - DEBUG;TRACE,MF_FRAMEWORK_VERSION_V4_2 - true - full - AnyCPU - false - prompt - false - false - - - OnOutputUpdated - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {F444D76A-7C89-4AD5-B7B6-1B8293A0DC13} - WebServer - - - + + + + Controller + Exe + Controller + {b69e3092-b931-443c-abe7-7e7b65f2a37f};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 9.0.21022 + 2.0 + {117848BD-213C-4AE4-8F58-D27F14DAA534} + v4.2 + $(MSBuildExtensionsPath32)\Microsoft\.NET Micro Framework\ + + + true + full + false + bin\Debug\ + TRACE;DEBUG;TRACE,MF_FRAMEWORK_VERSION_V4_2 + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + bin\Emulator\ + DEBUG;TRACE,MF_FRAMEWORK_VERSION_V4_2 + true + full + AnyCPU + false + prompt + false + false + + + OnOutputUpdated + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {58DB2B34-17A3-448D-A0B5-A5FEBEAC6F53} + NetduinoPlusTelnet + + + {71B2DDB7-063C-4866-BD82-83D06E22E225} + WebServer + + + copy $(ProjectDir)config.js g:\ -copy $(ProjectDir)fragments\* g:\fragments - + +copy $(ProjectDir)fragments\* g:\fragments\ + \ No newline at end of file diff --git a/Controller/Extensions.cs b/Controller/Extensions.cs index f8c9fc2..0366359 100644 --- a/Controller/Extensions.cs +++ b/Controller/Extensions.cs @@ -196,8 +196,8 @@ public static void SetFromNetwork(this DateTime dateTime, TimeSpan TimeZoneOffse // And http://nickstips.wordpress.com/2010/02/12/c-get-nist-internet-time/ // Time server list: http://tf.nist.gov/tf-cgi/servers.cgi - var ran = new Random(DateTime.Now.Millisecond); - var servers = new string[] { "time-a.nist.gov", "time-b.nist.gov", "nist1-la.ustiming.org", "nist1-chi.ustiming.org", "nist1-ny.ustiming.org", "time-nw.nist.gov" }; + var ran = new Random(DateTime.Now.Millisecond); + var servers = new string[] { "pool.ntp.org", "north-america.pool.ntp.org" }; // Try each server in random order to avoid blocked requests due to too frequent request for (int i = 0; i < servers.Length; i++) @@ -207,7 +207,9 @@ public static void SetFromNetwork(this DateTime dateTime, TimeSpan TimeZoneOffse // Open a Socket to a random time server var ep = new IPEndPoint(Dns.GetHostEntry(servers[ran.Next(servers.Length)]).AddressList[0], 123); - var s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + var s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + s.ReceiveTimeout = 5000; + s.SendTimeout = 5000; //s.Connect(ep); byte[] ntpData = new byte[48]; // RFC 2030 diff --git a/Controller/HtmlBuilder.cs b/Controller/HtmlBuilder.cs index cf6d4d2..6e5be21 100644 --- a/Controller/HtmlBuilder.cs +++ b/Controller/HtmlBuilder.cs @@ -17,21 +17,33 @@ class HtmlBuilder : IDisposable public void Dispose() { } private byte[] m_closeDiv; - private readonly string m_hostUrl = @"http://fishfornerds.com/files/"; + private readonly string m_hostUrl = @"files/"; private readonly string m_headerScriptFileName = "headerScripts.tmp"; private readonly string m_scriptCallFileName = "scriptCall.tmp"; private ArrayList m_controlPlugins; private ArrayList m_inputPlugins; private ArrayList m_outputPlugins; - + /// + /// Constructor, initializes temp files and necessary paths. + /// + /// + /// When the HtmlBuilder is initialized, check for any temp files and remove them. + /// This ensures that the index.htm file that is built doesn't have any errors from + /// the previous runs + /// public HtmlBuilder() { m_controlPlugins = new ArrayList(); m_inputPlugins = new ArrayList(); m_outputPlugins = new ArrayList(); m_closeDiv = Encoding.UTF8.GetBytes(""); + + // just delete it, don't check for exist, as no exception will be thrown on missing file + File.Delete(Controller.FragmentFolder + m_headerScriptFileName); + File.Delete(Controller.FragmentFolder + m_scriptCallFileName); } + /// /// Add required js and html for a specific plugin to the index generator /// @@ -47,7 +59,7 @@ public void AddPlugin(string _scriptName, PluginType _type, bool _local) script.Append(m_hostUrl); script.Append(_scriptName); script.Append(".min.js\" type=\"text/javascript\">"); - using (FileStream fs = new FileStream(Program.FragmentFolder + m_headerScriptFileName, FileMode.Append)) + using (FileStream fs = new FileStream(Controller.FragmentFolder + m_headerScriptFileName, FileMode.Append)) { byte[] text = script.ToBytes(); fs.Write(text, 0, text.Length); @@ -59,7 +71,7 @@ public void AddPlugin(string _scriptName, PluginType _type, bool _local) script.Append("$("); script.Append(_scriptName); script.Append("Init);"); - using (FileStream fs = new FileStream(Program.FragmentFolder + m_scriptCallFileName, FileMode.Append)) + using (FileStream fs = new FileStream(Controller.FragmentFolder + m_scriptCallFileName, FileMode.Append)) { byte[] text = script.ToBytes(); fs.Write(text, 0, text.Length); @@ -90,20 +102,20 @@ public void AddPlugin(string _scriptName, PluginType _type, bool _local) public void GenerateIndex() { byte[] stringBytes; - FileStream index = new FileStream(@"\SD\index.html", FileMode.Create); + FileStream index = new FileStream(Controller.IndexFile, FileMode.Create); - FileStream fragment = new FileStream(Program.FragmentFolder+"header.htm", FileMode.Open); + FileStream fragment = new FileStream(Controller.FragmentFolder+"header.htm", FileMode.Open); fragment.CopyTo(index); fragment.Close(); index.Flush(); // Header written, add JS Script links - fragment = new FileStream(Program.FragmentFolder + m_headerScriptFileName, FileMode.Open); + fragment = new FileStream(Controller.FragmentFolder + m_headerScriptFileName, FileMode.Open); fragment.CopyTo(index); fragment.Close(); index.Flush(); - fragment = new FileStream(Program.FragmentFolder+"body-start.htm", FileMode.Open); + fragment = new FileStream(Controller.FragmentFolder+"body-start.htm", FileMode.Open); fragment.CopyTo(index); fragment.Close(); index.Flush(); @@ -127,17 +139,17 @@ public void GenerateIndex() return; // close body - fragment = new FileStream(Program.FragmentFolder+"body-end.htm", FileMode.Open); + fragment = new FileStream(Controller.FragmentFolder+"body-end.htm", FileMode.Open); fragment.CopyTo(index); fragment.Close(); // add JS calls to initiate front end - fragment = new FileStream(Program.FragmentFolder + m_scriptCallFileName, FileMode.Open); + fragment = new FileStream(Controller.FragmentFolder + m_scriptCallFileName, FileMode.Open); fragment.CopyTo(index); fragment.Close(); // close document - fragment = new FileStream(Program.FragmentFolder+"footer.htm", FileMode.Open); + fragment = new FileStream(Controller.FragmentFolder+"footer.htm", FileMode.Open); fragment.CopyTo(index); fragment.Close(); @@ -146,8 +158,8 @@ public void GenerateIndex() index.Close(); // delete temp files used in building index - File.Delete(Program.FragmentFolder + m_headerScriptFileName); - File.Delete(Program.FragmentFolder + m_scriptCallFileName); + File.Delete(Controller.FragmentFolder + m_headerScriptFileName); + File.Delete(Controller.FragmentFolder + m_scriptCallFileName); } @@ -181,7 +193,7 @@ private bool WritePlugins(ref FileStream _index, PluginType _type) { foreach (string item in currArray) { - fragment = new FileStream(Program.PluginFolder + item + ".htm", FileMode.Open); + fragment = new FileStream(Controller.PluginFolder + item + ".htm", FileMode.Open); fragment.CopyTo(_index); fragment.Close(); } diff --git a/Controller/IPlugin.cs b/Controller/IPlugin.cs index 59ee723..d4fb3ea 100644 --- a/Controller/IPlugin.cs +++ b/Controller/IPlugin.cs @@ -1,40 +1,72 @@ -using System; -using System.Collections; - -namespace Controller -{ - public enum ThingSpeakFields : uint { Temperature = 1, pH = 2 } - public enum PluginType { Input, Output, Control } - - public abstract class Plugin : IDisposable - { - // Implementation for Disposable - public abstract void Dispose(); - } - - public abstract class InputPlugin : Plugin - { - // Timer Intervals specified in config file - public abstract TimeSpan TimerInterval { get; } - public abstract void TimerCallback(Object state); - public abstract void EventHandler(Object sender, IPluginData data); - } - - public abstract class OutputPlugin : Plugin - { - public abstract void EventHandler(Object sender, IPluginData data); - } - - public abstract class ControlPlugin : Plugin - { - public abstract void ExecuteControl(Object state); - public abstract Hashtable Commands(); - } - - public interface IPluginData - { - ThingSpeakFields DataType(); - string DataUnits(); - float GetValue(); - } +using System; +using System.Collections; + +namespace Controller +{ + public enum PluginType { Input, Output, Control } + + public abstract class Plugin : IDisposable + { + // Implementation for Disposable + public abstract void Dispose(); + } + + public abstract class InputPlugin : Plugin + { + // Timer Intervals specified in config file + public abstract TimeSpan TimerInterval { get; } + public abstract void TimerCallback(Object state); + public abstract void EventHandler(Object sender, IPluginData plugin); + public abstract bool ImplimentsEventHandler(); + } + + public abstract class OutputPlugin : Plugin + { + public abstract void EventHandler(Object sender, IPluginData plugin); + } + + public abstract class ControlPlugin : Plugin + { + public abstract void ExecuteControl(Object state); + public abstract CommandData[] Commands(); + public abstract void EventHandler(Object sender, IPluginData plugin); + public abstract bool ImplimentsEventHandler(); + } + + public interface IPluginData + { + PluginData[] GetData(); + void SetData(PluginData[] data); + } + + public class PluginData + { + public string Name; + public uint ThingSpeakFieldID; + public string UnitOFMeasurment; + public double Value = 0; + public bool LastReadSuccess = false; + + } + + public class CommandData + { + public TimeSpan FirstRun; + public bool Command; + public TimeSpan RepeatTimeSpan; + public TimeSpan DurationOn; + public TimeSpan DurationOff; + public int RelayID; + public string RelayName; + public string TimerType; + public double RangeMin; + public double RangeMax; + public bool Inverted; + public string RangeMetric; + public bool Enable; + public int PulseTime; + public int TimeBetweenPulses; + public DateTime NextPulseAfter; + } + } \ No newline at end of file diff --git a/Controller/JSON.cs b/Controller/JSON.cs index a55e78c..e8503f0 100644 --- a/Controller/JSON.cs +++ b/Controller/JSON.cs @@ -1,556 +1,556 @@ -using System; -using System.Collections; -using System.Text; -using System.IO; - -namespace Controller -{ - /// - /// This class encodes and decodes JSON strings. - /// Spec. details, see http://www.json.org/ - /// - /// JSON uses Arrays and Objects. These correspond here to the datatypes ArrayList and Hashtable. - /// All numbers are parsed to doubles. - /// Pulled from http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html - /// - /// - public class JSON - { - public const int TOKEN_NONE = 0; - public const int TOKEN_CURLY_OPEN = 1; - public const int TOKEN_CURLY_CLOSE = 2; - public const int TOKEN_SQUARED_OPEN = 3; - public const int TOKEN_SQUARED_CLOSE = 4; - public const int TOKEN_COLON = 5; - public const int TOKEN_COMMA = 6; - public const int TOKEN_STRING = 7; - public const int TOKEN_NUMBER = 8; - public const int TOKEN_TRUE = 9; - public const int TOKEN_FALSE = 10; - public const int TOKEN_NULL = 11; - - private const int BUILDER_CAPACITY = 2000; - - public static object JsonDecodeConfig(string configFile) - { - using (FileStream fs = new FileStream(configFile, FileMode.Open)) - { - using (StreamReader sr = new StreamReader(fs)) - { - // config.js is used by both the backend and frontend for configuration. - // The frontend requires a js var declaration in it, so strip the var components - // from the string - string configString = sr.ReadToEnd(); - configString = configString.Substring(11, configString.Length - 12); - bool success = true; - return JsonDecode(configString, ref success); - } - } - } - - /// - /// Decodes a JSON string stored in a text file. Assumes the string is defined as a JS variable. - /// - /// File to open for decoding - /// - public static object JsonDecodeFromVar(string file) - { - using (FileStream fs = new FileStream(file, FileMode.Open)) - { - using (StreamReader sr = new StreamReader(fs)) - { - string varString = sr.ReadToEnd(); - - // Find first location of { - int startAt = varString.IndexOf('{'); - varString = varString.Substring(startAt, varString.Length - (startAt-1)); - bool success = true; - return JsonDecode(varString, ref success); - } - } - } - - public static object JsonDecodeFromFile(string file) - { - using (FileStream fs = new FileStream(file, FileMode.Open)) - { - using (StreamReader sr = new StreamReader(fs)) - { - string configString = sr.ReadToEnd(); - bool success = true; - return JsonDecode(configString, ref success); - } - } - } - - /// - /// Parses the string json into a value - /// - /// A JSON string. - /// An ArrayList, a Hashtable, a double, a string, null, true, or false - public static object JsonDecode(string json) - { - bool success = true; - - return JsonDecode(json, ref success); - } - - /// - /// Parses the string json into a value; and fills 'success' with the successfullness of the parse. - /// - /// A JSON string. - /// Successful parse? - /// An ArrayList, a Hashtable, a double, a string, null, true, or false - public static object JsonDecode(string json, ref bool success) - { - success = true; - if (json != null) { - char[] charArray = json.ToCharArray(); - int index = 0; - object value = ParseValue(charArray, ref index, ref success); - return value; - } else { - return null; - } - } - - /// - /// Converts a Hashtable / ArrayList object into a JSON string - /// - /// A Hashtable / ArrayList - /// A JSON encoded string, or null if object 'json' is not serializable - public static string JsonEncode(object json) - { - StringBuilder builder = new StringBuilder(BUILDER_CAPACITY); - bool success = SerializeValue(json, builder); - return (success ? builder.ToString() : null); - } - - protected static Hashtable ParseObject(char[] json, ref int index, ref bool success) - { - Hashtable table = new Hashtable(); - int token; - - // { - NextToken(json, ref index); - - bool done = false; - while (!done) { - token = LookAhead(json, index); - if (token == JSON.TOKEN_NONE) { - success = false; - return null; - } else if (token == JSON.TOKEN_COMMA) { - NextToken(json, ref index); - } else if (token == JSON.TOKEN_CURLY_CLOSE) { - NextToken(json, ref index); - return table; - } else { - - // name - string name = ParseString(json, ref index, ref success); - if (!success) { - success = false; - return null; - } - - // : - token = NextToken(json, ref index); - if (token != JSON.TOKEN_COLON) { - success = false; - return null; - } - - // value - object value = ParseValue(json, ref index, ref success); - if (!success) { - success = false; - return null; - } - - table[name] = value; - } - } - - return table; - } - - protected static ArrayList ParseArray(char[] json, ref int index, ref bool success) - { - ArrayList array = new ArrayList(); - - // [ - NextToken(json, ref index); - - bool done = false; - while (!done) { - int token = LookAhead(json, index); - if (token == JSON.TOKEN_NONE) { - success = false; - return null; - } else if (token == JSON.TOKEN_COMMA) { - NextToken(json, ref index); - } else if (token == JSON.TOKEN_SQUARED_CLOSE) { - NextToken(json, ref index); - break; - } else { - object value = ParseValue(json, ref index, ref success); - if (!success) { - return null; - } - - array.Add(value); - } - } - - return array; - } - - protected static object ParseValue(char[] json, ref int index, ref bool success) - { - switch (LookAhead(json, index)) { - case JSON.TOKEN_STRING: - return ParseString(json, ref index, ref success); - case JSON.TOKEN_NUMBER: - return ParseNumber(json, ref index, ref success); - case JSON.TOKEN_CURLY_OPEN: - return ParseObject(json, ref index, ref success); - case JSON.TOKEN_SQUARED_OPEN: - return ParseArray(json, ref index, ref success); - case JSON.TOKEN_TRUE: - NextToken(json, ref index); - return true; - case JSON.TOKEN_FALSE: - NextToken(json, ref index); - return false; - case JSON.TOKEN_NULL: - NextToken(json, ref index); - return null; - case JSON.TOKEN_NONE: - break; - } - - success = false; - return null; - } - - protected static string ParseString(char[] json, ref int index, ref bool success) - { - StringBuilder s = new StringBuilder(BUILDER_CAPACITY); - char c; - - EatWhitespace(json, ref index); - - // " - c = json[index++]; - - bool complete = false; - while (!complete) { - - if (index == json.Length) { - break; - } - - c = json[index++]; - if (c == '"') { - complete = true; - break; - } else if (c == '\\') { - - if (index == json.Length) { - break; - } - c = json[index++]; - if (c == '"') { - s.Append('"'); - } else if (c == '\\') { - s.Append('\\'); - } else if (c == '/') { - s.Append('/'); - } else if (c == 'b') { - s.Append('\b'); - } else if (c == 'f') { - s.Append('\f'); - } else if (c == 'n') { - s.Append('\n'); - } else if (c == 'r') { - s.Append('\r'); - } else if (c == 't') { - s.Append('\t'); - } else if (c == 'u') { - int remainingLength = json.Length - index; - if (remainingLength >= 4) { - // parse the 32 bit hex into an integer codepoint - uint codePoint = UInt32.Parse(new string(json, index, 4)); - // convert the integer codepoint to a unicode char and add to string - s.Append(codePoint); - // skip 4 chars - index += 4; - } else { - break; - } - } - - } else { - s.Append(c); - } - - } - - if (!complete) { - success = false; - return null; - } - - return s.ToString(); - } - - protected static double ParseNumber(char[] json, ref int index, ref bool success) - { - EatWhitespace(json, ref index); - - int lastIndex = GetLastIndexOfNumber(json, index); - int charLength = (lastIndex - index) + 1; - - double number; - success = Double.TryParse(new string(json, index, charLength), out number); - - index = lastIndex + 1; - return number; - } - - protected static int GetLastIndexOfNumber(char[] json, int index) - { - int lastIndex; - - for (lastIndex = index; lastIndex < json.Length; lastIndex++) { - if ("0123456789+-.eE".IndexOf(json[lastIndex]) == -1) { - break; - } - } - return lastIndex - 1; - } - - protected static void EatWhitespace(char[] json, ref int index) - { - for (; index < json.Length; index++) { - if (" \t\n\r".IndexOf(json[index]) == -1) { - break; - } - } - } - - protected static int LookAhead(char[] json, int index) - { - int saveIndex = index; - return NextToken(json, ref saveIndex); - } - - protected static int NextToken(char[] json, ref int index) - { - EatWhitespace(json, ref index); - - if (index == json.Length) { - return JSON.TOKEN_NONE; - } - - char c = json[index]; - index++; - switch (c) { - case '{': - return JSON.TOKEN_CURLY_OPEN; - case '}': - return JSON.TOKEN_CURLY_CLOSE; - case '[': - return JSON.TOKEN_SQUARED_OPEN; - case ']': - return JSON.TOKEN_SQUARED_CLOSE; - case ',': - return JSON.TOKEN_COMMA; - case '"': - return JSON.TOKEN_STRING; - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - case '-': - return JSON.TOKEN_NUMBER; - case ':': - return JSON.TOKEN_COLON; - } - index--; - - int remainingLength = json.Length - index; - - // false - if (remainingLength >= 5) { - if (json[index] == 'f' && - json[index + 1] == 'a' && - json[index + 2] == 'l' && - json[index + 3] == 's' && - json[index + 4] == 'e') { - index += 5; - return JSON.TOKEN_FALSE; - } - } - - // true - if (remainingLength >= 4) { - if (json[index] == 't' && - json[index + 1] == 'r' && - json[index + 2] == 'u' && - json[index + 3] == 'e') { - index += 4; - return JSON.TOKEN_TRUE; - } - } - - // null - if (remainingLength >= 4) { - if (json[index] == 'n' && - json[index + 1] == 'u' && - json[index + 2] == 'l' && - json[index + 3] == 'l') { - index += 4; - return JSON.TOKEN_NULL; - } - } - - return JSON.TOKEN_NONE; - } - - protected static bool SerializeValue(object value, StringBuilder builder) - { - bool success = true; - - if (value is string) { - success = SerializeString((string)value, builder); - } else if (value is Hashtable) { - success = SerializeObject((Hashtable)value, builder); - } else if (value is ArrayList) { - success = SerializeArray((ArrayList)value, builder); - } else if ((value is Boolean) && ((Boolean)value == true)) { - builder.Append("true"); - } else if ((value is Boolean) && ((Boolean)value == false)) { - builder.Append("false"); - } else if (value is ValueType) { - // thanks to ritchie for pointing out ValueType to me - success = SerializeNumber(Convert.ToDouble(value.ToString()), builder); - } else if (value == null) { - builder.Append("null"); - } else { - success = false; - } - return success; - } - - protected static bool SerializeObject(Hashtable anObject, StringBuilder builder) - { - builder.Append("{"); - - //IEnumerator e = anObject.GetEnumerator(); - bool first = true; - foreach (DictionaryEntry e in anObject) - { - string key = e.Key.ToString(); - object value = e.Value; - - if (!first) - builder.Append(", "); - - SerializeString(key, builder); - builder.Append(":"); - if (!SerializeValue(value, builder)) - return false; - - first = false; - } - /* - while (e.MoveNext()) { - - string key = ((DictionaryEntry)e).Key.ToString(); - object value = e.Value; - - if (!first) { - builder.Append(", "); - } - - SerializeString(key, builder); - builder.Append(":"); - if (!SerializeValue(value, builder)) { - return false; - } - - first = false; - } - */ - builder.Append("}"); - return true; - } - - protected static bool SerializeArray(ArrayList anArray, StringBuilder builder) - { - builder.Append("["); - - bool first = true; - for (int i = 0; i < anArray.Count; i++) { - object value = anArray[i]; - - if (!first) { - builder.Append(", "); - } - - if (!SerializeValue(value, builder)) { - return false; - } - - first = false; - } - - builder.Append("]"); - return true; - } - - protected static bool SerializeString(string aString, StringBuilder builder) - { - builder.Append("\""); - - char[] charArray = aString.ToCharArray(); - for (int i = 0; i < charArray.Length; i++) { - char c = charArray[i]; - if (c == '"') { - builder.Append("\\\""); - } else if (c == '\\') { - builder.Append("\\\\"); - } else if (c == '\b') { - builder.Append("\\b"); - } else if (c == '\f') { - builder.Append("\\f"); - } else if (c == '\n') { - builder.Append("\\n"); - } else if (c == '\r') { - builder.Append("\\r"); - } else if (c == '\t') { - builder.Append("\\t"); - } else { - if ((int)c >= 32 && (int)c <=126) - { - builder.Append(c); - } else { - int codepoint = Convert.ToInt32(c.ToString()); - builder.Append("\\u" + codepoint.ToString("{0:X}").PadLeft(4, '0')); - } - } - } - - builder.Append("\""); - return true; - } - - protected static bool SerializeNumber(double number, StringBuilder builder) - { - - builder.Append(number.ToString()); - return true; - } - } -} - +using System; +using System.Collections; +using System.Text; +using System.IO; + +namespace Controller +{ + /// + /// This class encodes and decodes JSON strings. + /// Spec. details, see http://www.json.org/ + /// + /// JSON uses Arrays and Objects. These correspond here to the datatypes ArrayList and Hashtable. + /// All numbers are parsed to doubles. + /// Pulled from http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html + /// + /// + public class JSON + { + public const int TOKEN_NONE = 0; + public const int TOKEN_CURLY_OPEN = 1; + public const int TOKEN_CURLY_CLOSE = 2; + public const int TOKEN_SQUARED_OPEN = 3; + public const int TOKEN_SQUARED_CLOSE = 4; + public const int TOKEN_COLON = 5; + public const int TOKEN_COMMA = 6; + public const int TOKEN_STRING = 7; + public const int TOKEN_NUMBER = 8; + public const int TOKEN_TRUE = 9; + public const int TOKEN_FALSE = 10; + public const int TOKEN_NULL = 11; + + private const int BUILDER_CAPACITY = 2000; + + public static object JsonDecodeConfig(string configFile) + { + using (FileStream fs = new FileStream(configFile, FileMode.Open)) + { + using (StreamReader sr = new StreamReader(fs)) + { + // config.js is used by both the backend and frontend for configuration. + // The frontend requires a js var declaration in it, so strip the var components + // from the string + string configString = sr.ReadToEnd(); + configString = configString.Substring(12, configString.Length - 13); + bool success = true; + return JsonDecode(configString, ref success); + } + } + } + + /// + /// Decodes a JSON string stored in a text file. Assumes the string is defined as a JS variable. + /// + /// File to open for decoding + /// + public static object JsonDecodeFromVar(string file) + { + using (FileStream fs = new FileStream(file, FileMode.Open)) + { + using (StreamReader sr = new StreamReader(fs)) + { + string varString = sr.ReadToEnd(); + + // Find first location of { + int startAt = varString.IndexOf('{'); + varString = varString.Substring(startAt, varString.Length - startAt); + bool success = true; + return JsonDecode(varString, ref success); + } + } + } + + public static object JsonDecodeFromFile(string file) + { + using (FileStream fs = new FileStream(file, FileMode.Open)) + { + using (StreamReader sr = new StreamReader(fs)) + { + string configString = sr.ReadToEnd(); + bool success = true; + return JsonDecode(configString, ref success); + } + } + } + + /// + /// Parses the string json into a value + /// + /// A JSON string. + /// An ArrayList, a Hashtable, a double, a string, null, true, or false + public static object JsonDecode(string json) + { + bool success = true; + + return JsonDecode(json, ref success); + } + + /// + /// Parses the string json into a value; and fills 'success' with the successfullness of the parse. + /// + /// A JSON string. + /// Successful parse? + /// An ArrayList, a Hashtable, a double, a string, null, true, or false + public static object JsonDecode(string json, ref bool success) + { + success = true; + if (json != null) { + char[] charArray = json.ToCharArray(); + int index = 0; + object value = ParseValue(charArray, ref index, ref success); + return value; + } else { + return null; + } + } + + /// + /// Converts a Hashtable / ArrayList object into a JSON string + /// + /// A Hashtable / ArrayList + /// A JSON encoded string, or null if object 'json' is not serializable + public static string JsonEncode(object json) + { + StringBuilder builder = new StringBuilder(BUILDER_CAPACITY); + bool success = SerializeValue(json, builder); + return (success ? builder.ToString() : null); + } + + protected static Hashtable ParseObject(char[] json, ref int index, ref bool success) + { + Hashtable table = new Hashtable(); + int token; + + // { + NextToken(json, ref index); + + bool done = false; + while (!done) { + token = LookAhead(json, index); + if (token == JSON.TOKEN_NONE) { + success = false; + return null; + } else if (token == JSON.TOKEN_COMMA) { + NextToken(json, ref index); + } else if (token == JSON.TOKEN_CURLY_CLOSE) { + NextToken(json, ref index); + return table; + } else { + + // name + string name = ParseString(json, ref index, ref success); + if (!success) { + success = false; + return null; + } + + // : + token = NextToken(json, ref index); + if (token != JSON.TOKEN_COLON) { + success = false; + return null; + } + + // value + object value = ParseValue(json, ref index, ref success); + if (!success) { + success = false; + return null; + } + + table[name] = value; + } + } + + return table; + } + + protected static ArrayList ParseArray(char[] json, ref int index, ref bool success) + { + ArrayList array = new ArrayList(); + + // [ + NextToken(json, ref index); + + bool done = false; + while (!done) { + int token = LookAhead(json, index); + if (token == JSON.TOKEN_NONE) { + success = false; + return null; + } else if (token == JSON.TOKEN_COMMA) { + NextToken(json, ref index); + } else if (token == JSON.TOKEN_SQUARED_CLOSE) { + NextToken(json, ref index); + break; + } else { + object value = ParseValue(json, ref index, ref success); + if (!success) { + return null; + } + + array.Add(value); + } + } + + return array; + } + + protected static object ParseValue(char[] json, ref int index, ref bool success) + { + switch (LookAhead(json, index)) { + case JSON.TOKEN_STRING: + return ParseString(json, ref index, ref success); + case JSON.TOKEN_NUMBER: + return ParseNumber(json, ref index, ref success); + case JSON.TOKEN_CURLY_OPEN: + return ParseObject(json, ref index, ref success); + case JSON.TOKEN_SQUARED_OPEN: + return ParseArray(json, ref index, ref success); + case JSON.TOKEN_TRUE: + NextToken(json, ref index); + return true; + case JSON.TOKEN_FALSE: + NextToken(json, ref index); + return false; + case JSON.TOKEN_NULL: + NextToken(json, ref index); + return null; + case JSON.TOKEN_NONE: + break; + } + + success = false; + return null; + } + + protected static string ParseString(char[] json, ref int index, ref bool success) + { + StringBuilder s = new StringBuilder(BUILDER_CAPACITY); + char c; + + EatWhitespace(json, ref index); + + // " + c = json[index++]; + + bool complete = false; + while (!complete) { + + if (index == json.Length) { + break; + } + + c = json[index++]; + if (c == '"') { + complete = true; + break; + } else if (c == '\\') { + + if (index == json.Length) { + break; + } + c = json[index++]; + if (c == '"') { + s.Append('"'); + } else if (c == '\\') { + s.Append('\\'); + } else if (c == '/') { + s.Append('/'); + } else if (c == 'b') { + s.Append('\b'); + } else if (c == 'f') { + s.Append('\f'); + } else if (c == 'n') { + s.Append('\n'); + } else if (c == 'r') { + s.Append('\r'); + } else if (c == 't') { + s.Append('\t'); + } else if (c == 'u') { + int remainingLength = json.Length - index; + if (remainingLength >= 4) { + // parse the 32 bit hex into an integer codepoint + uint codePoint = UInt32.Parse(new string(json, index, 4)); + // convert the integer codepoint to a unicode char and add to string + s.Append(codePoint); + // skip 4 chars + index += 4; + } else { + break; + } + } + + } else { + s.Append(c); + } + + } + + if (!complete) { + success = false; + return null; + } + + return s.ToString(); + } + + protected static double ParseNumber(char[] json, ref int index, ref bool success) + { + EatWhitespace(json, ref index); + + int lastIndex = GetLastIndexOfNumber(json, index); + int charLength = (lastIndex - index) + 1; + + double number; + success = Double.TryParse(new string(json, index, charLength), out number); + + index = lastIndex + 1; + return number; + } + + protected static int GetLastIndexOfNumber(char[] json, int index) + { + int lastIndex; + + for (lastIndex = index; lastIndex < json.Length; lastIndex++) { + if ("0123456789+-.eE".IndexOf(json[lastIndex]) == -1) { + break; + } + } + return lastIndex - 1; + } + + protected static void EatWhitespace(char[] json, ref int index) + { + for (; index < json.Length; index++) { + if (" \t\n\r".IndexOf(json[index]) == -1) { + break; + } + } + } + + protected static int LookAhead(char[] json, int index) + { + int saveIndex = index; + return NextToken(json, ref saveIndex); + } + + protected static int NextToken(char[] json, ref int index) + { + EatWhitespace(json, ref index); + + if (index == json.Length) { + return JSON.TOKEN_NONE; + } + + char c = json[index]; + index++; + switch (c) { + case '{': + return JSON.TOKEN_CURLY_OPEN; + case '}': + return JSON.TOKEN_CURLY_CLOSE; + case '[': + return JSON.TOKEN_SQUARED_OPEN; + case ']': + return JSON.TOKEN_SQUARED_CLOSE; + case ',': + return JSON.TOKEN_COMMA; + case '"': + return JSON.TOKEN_STRING; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case '-': + return JSON.TOKEN_NUMBER; + case ':': + return JSON.TOKEN_COLON; + } + index--; + + int remainingLength = json.Length - index; + + // false + if (remainingLength >= 5) { + if (json[index] == 'f' && + json[index + 1] == 'a' && + json[index + 2] == 'l' && + json[index + 3] == 's' && + json[index + 4] == 'e') { + index += 5; + return JSON.TOKEN_FALSE; + } + } + + // true + if (remainingLength >= 4) { + if (json[index] == 't' && + json[index + 1] == 'r' && + json[index + 2] == 'u' && + json[index + 3] == 'e') { + index += 4; + return JSON.TOKEN_TRUE; + } + } + + // null + if (remainingLength >= 4) { + if (json[index] == 'n' && + json[index + 1] == 'u' && + json[index + 2] == 'l' && + json[index + 3] == 'l') { + index += 4; + return JSON.TOKEN_NULL; + } + } + + return JSON.TOKEN_NONE; + } + + protected static bool SerializeValue(object value, StringBuilder builder) + { + bool success = true; + + if (value is string) { + success = SerializeString((string)value, builder); + } else if (value is Hashtable) { + success = SerializeObject((Hashtable)value, builder); + } else if (value is ArrayList) { + success = SerializeArray((ArrayList)value, builder); + } else if ((value is Boolean) && ((Boolean)value == true)) { + builder.Append("true"); + } else if ((value is Boolean) && ((Boolean)value == false)) { + builder.Append("false"); + } else if (value is ValueType) { + // thanks to ritchie for pointing out ValueType to me + success = SerializeNumber(Convert.ToDouble(value.ToString()), builder); + } else if (value == null) { + builder.Append("null"); + } else { + success = false; + } + return success; + } + + protected static bool SerializeObject(Hashtable anObject, StringBuilder builder) + { + builder.Append("{"); + + //IEnumerator e = anObject.GetEnumerator(); + bool first = true; + foreach (DictionaryEntry e in anObject) + { + string key = e.Key.ToString(); + object value = e.Value; + + if (!first) + builder.Append(", "); + + SerializeString(key, builder); + builder.Append(":"); + if (!SerializeValue(value, builder)) + return false; + + first = false; + } + /* + while (e.MoveNext()) { + + string key = ((DictionaryEntry)e).Key.ToString(); + object value = e.Value; + + if (!first) { + builder.Append(", "); + } + + SerializeString(key, builder); + builder.Append(":"); + if (!SerializeValue(value, builder)) { + return false; + } + + first = false; + } + */ + builder.Append("}"); + return true; + } + + protected static bool SerializeArray(ArrayList anArray, StringBuilder builder) + { + builder.Append("["); + + bool first = true; + for (int i = 0; i < anArray.Count; i++) { + object value = anArray[i]; + + if (!first) { + builder.Append(", "); + } + + if (!SerializeValue(value, builder)) { + return false; + } + + first = false; + } + + builder.Append("]"); + return true; + } + + protected static bool SerializeString(string aString, StringBuilder builder) + { + builder.Append("\""); + + char[] charArray = aString.ToCharArray(); + for (int i = 0; i < charArray.Length; i++) { + char c = charArray[i]; + if (c == '"') { + builder.Append("\\\""); + } else if (c == '\\') { + builder.Append("\\\\"); + } else if (c == '\b') { + builder.Append("\\b"); + } else if (c == '\f') { + builder.Append("\\f"); + } else if (c == '\n') { + builder.Append("\\n"); + } else if (c == '\r') { + builder.Append("\\r"); + } else if (c == '\t') { + builder.Append("\\t"); + } else { + if ((int)c >= 32 && (int)c <=126) + { + builder.Append(c); + } else { + int codepoint = Convert.ToInt32(c.ToString()); + builder.Append("\\u" + codepoint.ToString("{0:X}").PadLeft(4, '0')); + } + } + } + + builder.Append("\""); + return true; + } + + protected static bool SerializeNumber(double number, StringBuilder builder) + { + + builder.Append(number.ToString()); + return true; + } + } +} + diff --git a/Controller/OnBoardFlash.dat.smd b/Controller/OnBoardFlash.dat.smd new file mode 100644 index 0000000..b59450d Binary files /dev/null and b/Controller/OnBoardFlash.dat.smd differ diff --git a/Controller/PluginScheduler.cs b/Controller/PluginScheduler.cs index 121a913..3f6da32 100644 --- a/Controller/PluginScheduler.cs +++ b/Controller/PluginScheduler.cs @@ -1,147 +1,147 @@ -using System; -using Microsoft.SPOT; -using System.Threading; -using System.Collections; - -namespace Controller -{ - /// - /// Delegate that is called when a PluginTask hits it's scheduled interval - /// - /// Any extra data that is required for the callback to process correctly - public delegate void PluginEventHandler(object _state); - - public class PluginScheduler : IDisposable - { - /// - /// Required information to track Plugin Task execution - /// Not used outside the PluginScheduler, hence protected - /// - protected class PluginTask - { - public Delegate CallBack { get; set; } - public Object State { get; set; } - public Boolean Reschedule { get; set; } - public TimeSpan Interval { get; set; } - public PluginTask(Delegate _callback, object _state, bool _reschedule, TimeSpan _interval) - { - CallBack = (PluginEventHandler)Delegate.Combine(CallBack, _callback); - State = _state; - Reschedule = _reschedule; - Interval = _interval; - } - } - - /// - /// Timer that does all the heavy lifting - /// - private Timer m_Poller; - - /// - /// Interval to poll plugin list for execution - /// - private TimeSpan m_pollInterval = new TimeSpan(0, 0, 10); - - /// - /// Timespan formatted string with coarser resolution than the Timer. - /// This ensures mismatches on intervals are still hit. i.e. Minute test with missed minutes - /// The time format also acts as the task ID. Note, duplicate entries can exist - /// - private string m_timerFormat = "MMddHHmm"; - - /// - /// All tasks attached to the scheduler - /// - private ArrayList m_Tasks; - - /// - /// Object for thread locking - /// - private object m_Locker = new Object(); - - ~PluginScheduler() { Dispose(); } - public void Dispose() { m_Poller.Dispose(); m_Tasks = null; } - - /// - /// Constructor - /// - public PluginScheduler() - { - m_Poller = new Timer(PollTasks, null, -1, -1); - m_Tasks = new ArrayList(); - } - - /// - /// Start the scheduler polling - /// - public void Start() - { - lock (m_Locker) m_Poller.Change(m_pollInterval, m_pollInterval); - } - - /// - /// Stop the Scheduler from Polling - /// - public void Stop() - { - lock (m_Locker) m_Poller.Change(-1, -1); - } - - /// - /// Adds a task to the scheduler - /// - /// Callback to execute on matching time - /// State object to pass to the Callback - /// Timespan when to execute the Callback - /// Interval when to re-execute Callback, if any - /// Should this Task be continuously rescheduled - public void AddTask(Delegate _delegate, object _state, TimeSpan _execute, TimeSpan _interval, bool _continuous) - { - lock (m_Locker) - { - PluginTask task = new PluginTask(_delegate, _state, _continuous, _interval); - DateTime now = DateTime.Now; - now += _execute; - DictionaryEntry entry = new DictionaryEntry(now.ToString(m_timerFormat), task); - m_Tasks.Add(entry); - } - } - - /// - /// Main polling thread - /// - /// Any required state information - private void PollTasks(object _state) - { - DateTime now = DateTime.Now; - string s_now = now.ToString(m_timerFormat); - Debug.Print("Polling plugin list at: " + now.ToString(m_timerFormat)); - foreach (DictionaryEntry item in m_Tasks) - { - Debug.Print(item.Key.ToString()); - if (item.Key.Equals(s_now)) - { - Debug.Print("Executing task"); - // Execute task in Value - PluginTask task = (PluginTask)item.Value; - PluginEventHandler callback = (PluginEventHandler)task.CallBack; - if(callback != null) callback(task.State); - - if (task.Reschedule) - { - now += task.Interval; - item.Key = now.ToString(m_timerFormat); - Debug.Print("Rescheduling task for " + now.ToString()); - } - else - { - lock (m_Locker) - { - m_Tasks.Remove(item); - } - } - } - } - } - } +using System; +using Microsoft.SPOT; +using System.Threading; +using System.Collections; + +namespace Controller +{ + /// + /// Delegate that is called when a PluginTask hits it's scheduled interval + /// + /// Any extra data that is required for the callback to process correctly + public delegate void PluginEventHandler(object _state); + + public class PluginScheduler : IDisposable + { + /// + /// Required information to track Plugin Task execution + /// Not used outside the PluginScheduler, hence protected + /// + protected class PluginTask + { + public Delegate CallBack { get; set; } + public Object State { get; set; } + public Boolean Reschedule { get; set; } + public TimeSpan Interval { get; set; } + public PluginTask(Delegate _callback, object _state, bool _reschedule, TimeSpan _interval) + { + CallBack = (PluginEventHandler)Delegate.Combine(CallBack, _callback); + State = _state; + Reschedule = _reschedule; + Interval = _interval; + } + } + + /// + /// Timer that does all the heavy lifting + /// + private Timer m_Poller; + + /// + /// Interval to poll plugin list for execution + /// + private TimeSpan m_pollInterval = new TimeSpan(0, 0, 10); + + /// + /// Timespan formatted string with coarser resolution than the Timer. + /// This ensures mismatches on intervals are still hit. i.e. Minute test with missed minutes + /// The time format also acts as the task ID. Note, duplicate entries can exist + /// + private string m_timerFormat = "MMddHHmm"; + + /// + /// All tasks attached to the scheduler + /// + private ArrayList m_Tasks; + + /// + /// Object for thread locking + /// + private object m_Locker = new Object(); + + ~PluginScheduler() { Dispose(); } + public void Dispose() { m_Poller.Dispose(); m_Tasks = null; } + + /// + /// Constructor + /// + public PluginScheduler() + { + m_Poller = new Timer(PollTasks, null, -1, -1); + m_Tasks = new ArrayList(); + } + + /// + /// Start the scheduler polling + /// + public void Start() + { + lock (m_Locker) m_Poller.Change(m_pollInterval, m_pollInterval); + } + + /// + /// Stop the Scheduler from Polling + /// + public void Stop() + { + lock (m_Locker) m_Poller.Change(-1, -1); + } + + /// + /// Adds a task to the scheduler + /// + /// Callback to execute on matching time + /// State object to pass to the Callback + /// Timespan when to execute the Callback + /// Interval when to re-execute Callback, if any + /// Should this Task be continuously rescheduled + public void AddTask(Delegate _delegate, object _state, TimeSpan _execute, TimeSpan _interval, bool _continuous) + { + lock (m_Locker) + { + PluginTask task = new PluginTask(_delegate, _state, _continuous, _interval); + DateTime now = DateTime.Now; + now += _execute; + DictionaryEntry entry = new DictionaryEntry(now.ToString(m_timerFormat), task); + m_Tasks.Add(entry); + } + } + + /// + /// Main polling thread + /// + /// Any required state information + private void PollTasks(object _state) + { + DateTime now = DateTime.Now; + int s_now = int.Parse(now.ToString(m_timerFormat)); + Debug.Print("Polling plugin list at: " + now.ToString(m_timerFormat)); + foreach (DictionaryEntry item in m_Tasks) + { + //Debug.Print("Checking Item Key: " + item.Key.ToString()); + if (int.Parse(item.Key.ToString()) <= s_now) + { + Debug.Print("Executing task"); + // Execute task in Value + PluginTask task = (PluginTask)item.Value; + PluginEventHandler callback = (PluginEventHandler)task.CallBack; + if(callback != null) callback(task.State); + + if (task.Reschedule) + { + now = DateTime.Now + task.Interval; + item.Key = now.ToString(m_timerFormat); + Debug.Print("Rescheduling task for " + now.ToString()); + } + else + { + lock (m_Locker) + { + m_Tasks.Remove(item); + } + } + } + } + } + } } \ No newline at end of file diff --git a/Controller/Program.cs b/Controller/Program.cs index a1440ff..f7c443d 100644 --- a/Controller/Program.cs +++ b/Controller/Program.cs @@ -1,316 +1,315 @@ -using System; -using System.Collections; -using System.IO; -using System.Reflection; -using System.Threading; -using Microsoft.SPOT; -using Microsoft.SPOT.Hardware; -using SecretLabs.NETMF.Hardware.NetduinoPlus; -using WebServer; - - -namespace Controller -{ - /// - /// Delegate for Output Plugins - /// - /// Who sent up the data - /// Data sent up from Input Plugin - public delegate void OutputPluginEventHandler(Object _sender, IPluginData _data); - - /// - /// Delegate for Input Plugins - /// - /// Data sent up from input plugin - public delegate void InputDataAvailable(IPluginData _data); - - /// - /// Delegate to handle Web Responses - /// - /// Any necessary data to complete the response - public delegate void WebResponseEventHandler(Object _sender); - - public class Program - { - public const string PluginFolder = @"\SD\plugins\"; - public const string FragmentFolder = @"\SD\fragments\"; - public const string ConfigFile = @"\SD\config.js"; - - /// - /// Utility object to build any static html that can be built on boot - /// Saves computation time where possible - /// - private static HtmlBuilder m_htmlBuilder; - - /// - /// Scheduler for Plugin tasks - /// - private static PluginScheduler m_pluginScheduler; - - /// - /// Collection of registered event handlers, mostly for dealing with web requests - /// - private static EventHandlerList m_eventHandlerList = new EventHandlerList(); - - /// - /// Handle to attach to Input Plugin timers. This event will be raised when an Input Plugin - /// is processed to trigger Output Plugins to run - /// - private static InputDataAvailable m_inputAvailable = new InputDataAvailable(DataAvailable); - - - /// - /// Delegate for signaling output plugins that data is available. - /// This is called from inside each Input Plugin Callback - /// - /// data passed up from input plugin - private static void DataAvailable(IPluginData _data) - { - // data should be available in the queue - // raise the event to handle it. - OutputPluginEventHandler ope = (OutputPluginEventHandler)m_eventHandlerList["OutputPlugins"]; - - // walk through all available output plugins - if (ope != null) ope(ope, _data); - } - - /// - /// Delegate to process web requests - /// - /// Request item received from Listener - /// Very much WIP - private static void WebCommandReceived(Request _request) - { - try - { - string requestString = _request.BaseUri.Substring(1); // skip leading slash - - // check ResponseHandlerList for matching response - WebResponseEventHandler handler = (WebResponseEventHandler)m_eventHandlerList[requestString]; - if (handler != null) - { - // Call the matched handler with the Request object. - // - /*handler(new DictionaryEntry(_request.Querystring["relay"].ToString(), - _request.Querystring["status"].ToString()));*/ - // - } - else - { - throw new NullReferenceException("No matching Response Handler found"); - } - /* - string content = HtmlGeneral.HtmlStart + "

Success

" + HtmlGeneral.HtmlEnd; - string header = HttpGeneral.GetHttpHeader(content.Length, "text/html", 10); - result = header + content; - Debug.Print("\t\trequest.URI="+request.Uri); - * */ - } - catch (Exception ex) - { - Debug.Print(ex.StackTrace); - /* - string content = HtmlGeneral.HtmlStart + "

500 server error.

" + "

Uri: " + request.Uri + "

"; - content += "

Error: " + ex.StackTrace + "

" + HtmlGeneral.HtmlEnd; - string header = HttpGeneral.Get500Header(content.Length); - result = header + content; - * */ - } - } - - - public static void Main() - { - // Initialize required components - bootstrap(); - // All plugins have been spun out and are running - - // - // All web server components are still very WIP, not functional - // Startup Web Frontend - // Listener webServer = new Listener(WebCommandReceived); - // - - Debug.EnableGCMessages(true); - Debug.Print(Debug.GC(true) + " bytes"); - - // Blink LED to show we're still responsive - OutputPort led = new OutputPort(Pins.ONBOARD_LED, false); - while (true) - { - led.Write(!led.Read()); - Thread.Sleep(500); - } - } - - private static void bootstrap() - { - /* - * Unfortunately, the DS1307 seems to be causing trouble with any AnalogInputs - * when being read from in I2C. Not sure why....using an NST server instead - */ - - // Set system time - //DateTime.Now.SetFromNetwork(new TimeSpan(-4, 0, 0)); - //DS1307 clock = new DS1307(); - //clock.TwelveHourMode = false; - //Utility.SetLocalTime(clock.CurrentDateTime); - //clock.Dispose(); - - m_htmlBuilder = new HtmlBuilder(); - m_eventHandlerList = new EventHandlerList(); - m_pluginScheduler = new PluginScheduler(); - - // Each key in 'config' is a collection of plugin types (input, output, control), - // so pull out of the root element. - Hashtable config = ((Hashtable)JSON.JsonDecodeConfig(ConfigFile))["config"] as Hashtable; - - // parse each plugin type - foreach (string pluginType in config.Keys) - ParseConfig(config[pluginType] as Hashtable, pluginType); - - config = null; - // config parsed, write out html index - m_htmlBuilder.GenerateIndex(); - m_htmlBuilder.Dispose(); - Debug.GC(true); - - m_pluginScheduler.Start(); - } - - /// - /// Web Frontend POSTs the JSON config as a string on save. - /// Extract string from Request and overwrite config file with new values - /// - /// PostRequest received from ResponseHandler - /// Very WIP - - /* - private static void SaveConfig(object _request) - { - PostRequest postRequest = (PostRequest)_request; - using (FileStream fs = new FileStream(ConfigFile, FileMode.Create)) - { - StringBuilder sb = new StringBuilder(); - sb.Append("var config="); - - } - } - */ - - /// - /// JSON Object contains nested components which need to be parsed down to indvidual plugin instructions. - /// This is done recursively to load all necessary plugins - /// - /// Current Hashtable being processed - /// Plugin type being processed - /// Name of Plugin being searched for - private static void ParseConfig(Hashtable _section, string _type = null, string _name = null) - { - foreach (string name in _section.Keys) - { - if (_section[name] is Hashtable) - ParseConfig((Hashtable)_section[name], _type, name); - else - { - // reached bottom of config tree, pass the Hashtable to constructors - if (_section["enabled"].ToString() == "true") - LoadPlugin(_name, _type, _section); - - // Include all plugins in web front end, regardless of status, to allow web front end - // to enable/disable properly - switch (_type) - { - case "input": - m_htmlBuilder.AddPlugin(_name, PluginType.Input, false); - break; - case "output": - m_htmlBuilder.AddPlugin(_name, PluginType.Output, false); - break; - case "control": - m_htmlBuilder.AddPlugin(_name, PluginType.Control, false); - break; - default: - break; - } - return; - } - } - } - - /// - /// Load the assembly dynamically from the SD Card, attach any necessary handlers, and update the web front end - /// to provide config options - /// - /// Name of the Plugin - /// Class of plugin - /// - private static void LoadPlugin(string _name, string _type, Hashtable _config) - { - try - { - using (FileStream fs = new FileStream(PluginFolder + _name + ".pe", FileMode.Open, FileAccess.Read)) - { - // Create an assembly - byte[] pluginBytes = new byte[(int)fs.Length]; - fs.Read(pluginBytes, 0, (int)fs.Length); - Assembly asm = Assembly.Load(pluginBytes); - - foreach (Type type in asm.GetTypes()) - { - Debug.Print(type.FullName); - if (type.FullName.Contains(_name)) - { - // call the constructor with the hashtable as constructor - // This allows individual plugins to parse out the components they need, - // and the main application doesn't need to know how each needs to be called - object plugin = (object)type.GetConstructor(new[] { typeof(object) }).Invoke(new object[] { _config }); - switch (_type) - { - case "input": - // Input plugins should spin out a timer - InputPlugin ip = (InputPlugin)plugin; - m_pluginScheduler.AddTask( - new PluginEventHandler(ip.TimerCallback), - m_inputAvailable, - ip.TimerInterval, - ip.TimerInterval, - true); - - // There is a special case with the pH plugin. The pH Stamp can receive a temperature - // reading to make the pH more accurate. In order to properly update the value, the pH - // plugin registers an output event to catch a temperature update. - if (_name.Equals("pH")) - m_eventHandlerList.AddHandler("OutputPlugins", (OutputPluginEventHandler)ip.EventHandler); - - break; - case "output": - // Output plugins need to register an event handler - OutputPlugin op = (OutputPlugin)plugin; - m_eventHandlerList.AddHandler("OutputPlugins", (OutputPluginEventHandler)op.EventHandler); - break; - case "control": - // Control Plugins contain a command set that is parsed out into individual timers - // They also register a Web Response Handler to allow the web front end to call ExecuteControl - ControlPlugin cp = (ControlPlugin)plugin; - foreach (DictionaryEntry item in cp.Commands()) - m_pluginScheduler.AddTask(new PluginEventHandler(cp.ExecuteControl), - item.Value, - (TimeSpan)item.Key, - new TimeSpan(24, 0, 0), // assuming controls should repeat every 24 hours - true); - - m_eventHandlerList.AddHandler(_name, new WebResponseEventHandler(cp.ExecuteControl)); - break; - default: - break; - } - } - } - } - } - catch (IOException) { throw; } - return; - } - } +using System; +using System.Collections; +using System.IO; +using System.Reflection; +using System.Threading; +using Microsoft.SPOT; +using Microsoft.SPOT.Hardware; +using Microsoft.SPOT.Net; +using Microsoft.SPOT.Net.NetworkInformation; +using SecretLabs.NETMF.Hardware.Netduino; +using Webserver; +using NetduinoPlusTelnet; + +namespace Controller +{ + /// + /// Delegate for Output Plugins + /// + /// Who sent up the data + /// Data sent up from Input Plugin + public delegate void OutputPluginEventHandler(Object _sender, IPluginData _data); + + /// + /// Delegate for Input Plugins + /// + /// Data sent up from input plugin + public delegate void InputDataAvailable(IPluginData _data); + + /// + /// Delegate to handle Web Responses + /// + /// Any necessary data to complete the response + public delegate void WebResponseEventHandler(Object _sender); + + public class Controller + { + public const string PluginFolder = @"\SD\plugins\"; + public const string FragmentFolder = @"\SD\fragments\"; + public const string ConfigFile = @"\SD\config.js"; + public const string IndexFile = @"\SD\index.html"; + + /// + /// Utility object to build any static html that can be built on boot + /// Saves computation time where possible + /// + private static HtmlBuilder m_htmlBuilder; + + /// + /// Scheduler for Plugin tasks + /// + private static PluginScheduler m_pluginScheduler; + + /// + /// Collection of registered event handlers, mostly for dealing with web requests + /// + private static EventHandlerList m_eventHandlerList = new EventHandlerList(); + + /// + /// Handle to attach to Input Plugin timers. This event will be raised when an Input Plugin + /// is processed to trigger Output Plugins to run + /// + private static InputDataAvailable m_inputAvailable = new InputDataAvailable(DataAvailable); + + + /// + /// Delegate for signaling output plugins that data is available. + /// This is called from inside each Input Plugin Callback + /// + /// data passed up from input plugin + private static void DataAvailable(IPluginData _data) + { + // data should be available in the queue + // raise the event to handle it. + OutputPluginEventHandler ope = (OutputPluginEventHandler)m_eventHandlerList["OutputPlugins"]; + + // walk through all available output plugins + if (ope != null) ope(ope, _data); + } + + + public static void Main() + { + // Initialize required components + bootstrap(); + // All plugins have been spun out and are running + + // + // All web server components are still very WIP, not functional + // Startup Web Frontend + Server WebServer = new Server(); + + //Start Telnet Server. Used for Calibration of sensors + TelnetServer telnetTest = new TelnetServer(timeout: 60); + telnetTest.begin(false); + + Debug.EnableGCMessages(true); + Debug.Print(Debug.GC(true) + " bytes"); + + // Blink LED to show we're still responsive + OutputPort led = new OutputPort(Pins.ONBOARD_LED, false); + while (true) + { + led.Write(!led.Read()); + Thread.Sleep(500); + } + } + + private static void bootstrap() + { + /* + * Unfortunately, the DS1307 seems to be causing trouble with any AnalogInputs + * when being read from in I2C. Not sure why....using an NST server instead + */ + NetworkInterface NetworkInt = Microsoft.SPOT.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces()[0]; + //Set Static IP + NetworkInt.EnableStaticIP("192.168.3.20","255.255.255.0","192.168.3.1"); + Debug.Print("IP Address is: " + Microsoft.SPOT.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces()[0].IPAddress); + + // Set system time + Thread.Sleep(5000); + DateTime.Now.SetFromNetwork(new TimeSpan(-5, 0, 0)); + Debug.Print("The current system time was set to " + DateTime.Now.ToString() + " from the network."); + //DS1307 clock = new DS1307(); + //clock.TwelveHourMode = false; + //Utility.SetLocalTime(clock.CurrentDateTime); + //clock.Dispose(); + + m_htmlBuilder = new HtmlBuilder(); + m_eventHandlerList = new EventHandlerList(); + m_pluginScheduler = new PluginScheduler(); + + // Each key in 'config' is a collection of plugin types (input, output, control), + // so pull out of the root element. + Hashtable config = ((Hashtable)JSON.JsonDecodeConfig(ConfigFile))["config"] as Hashtable; + + // parse each plugin type + foreach (string pluginType in config.Keys) + { + ParseConfig(config[pluginType] as Hashtable, pluginType); + } + + config = null; + // config parsed, write out html index + m_htmlBuilder.GenerateIndex(); + m_htmlBuilder.Dispose(); + Debug.GC(true); + + m_pluginScheduler.Start(); + } + + /// + /// Web Frontend POSTs the JSON config as a string on save. + /// Extract string from Request and overwrite config file with new values + /// + /// PostRequest received from ResponseHandler + /// Very WIP + + /* + private static void SaveConfig(object _request) + { + PostRequest postRequest = (PostRequest)_request; + using (FileStream fs = new FileStream(ConfigFile, FileMode.Create)) + { + StringBuilder sb = new StringBuilder(); + sb.Append("var config="); + + } + } + */ + + /// + /// JSON Object contains nested components which need to be parsed down to indvidual plugin instructions. + /// This is done recursively to load all necessary plugins + /// + /// Current Hashtable being processed + /// Plugin type being processed + /// Name of Plugin being searched for + private static void ParseConfig(Hashtable _section, string _type = null, string _name = null) + { + foreach (string name in _section.Keys) + { + if (_section[name] is Hashtable) + ParseConfig((Hashtable)_section[name], _type, name); + else + { + // reached bottom of config tree, pass the Hashtable to constructors + if (_section["enabled"].ToString() == "true") + LoadPlugin(_name, _type, _section); + + // Include all plugins in web front end, regardless of status, to allow web front end + // to enable/disable properly + switch (_type) + { + case "input": + m_htmlBuilder.AddPlugin(_name, PluginType.Input, false); + break; + case "output": + m_htmlBuilder.AddPlugin(_name, PluginType.Output, false); + break; + case "control": + m_htmlBuilder.AddPlugin(_name, PluginType.Control, false); + break; + default: + break; + } + return; + } + } + } + + /// + /// Load the assembly dynamically from the SD Card, attach any necessary handlers, and update the web front end + /// to provide config options + /// + /// Name of the Plugin + /// Class of plugin + /// + private static void LoadPlugin(string _name, string _type, Hashtable _config) + { + try + { + using (FileStream fs = new FileStream(PluginFolder + _name + ".pe", FileMode.Open, FileAccess.Read)) + { + // Create an assembly + byte[] pluginBytes = new byte[(int)fs.Length]; + fs.Read(pluginBytes, 0, (int)fs.Length); + Assembly asm = Assembly.Load(pluginBytes); + + foreach (Type type in asm.GetTypes()) + { + Debug.Print(type.FullName); + if (type.FullName.Contains(_name)) + { + // call the constructor with the hashtable as constructor + // This allows individual plugins to parse out the components they need, + // and the main application doesn't need to know how each needs to be called + object plugin; + switch(_name) + { + //case "Temperature": + //plugin = new Plugins.Temperature(_config); + // break; + //case "AquariumStatus": + // plugin=new Plugins.AquariumStatus(_config); + // break; + //case "CO2": + // plugin = new Plugins.CO2(_config); + // break; + //case "ElectricalConductivity": + // plugin = new Plugins.ElectricalConductivity(_config); + // break; + //case "Thingspeak": + // plugin = new Plugins.Thingspeak(_config); + // break; + //case "Relays": + // plugin = new Plugins.Relays(_config); + // break; + default: + plugin = (object)type.GetConstructor(new[] { typeof(object) }).Invoke(new object[] { _config }); + break; + } + switch (_type) + { + case "input": + // Input plugins should spin out a timer + InputPlugin ip = (InputPlugin)plugin; + m_pluginScheduler.AddTask( + new PluginEventHandler(ip.TimerCallback), + m_inputAvailable, + ip.TimerInterval, + ip.TimerInterval, + true); + + // There is a special case with some plugins. The pH Stamp can receive a temperature + // reading to make the pH more accurate. In order to properly update the value, the pH + // plugin registers an output event to catch a temperature update. + if (ip.ImplimentsEventHandler()) + m_eventHandlerList.AddHandler("OutputPlugins", (OutputPluginEventHandler)ip.EventHandler); + break; + case "output": + OutputPlugin op = (OutputPlugin)plugin; + // Output plugins need to register an event handler + m_eventHandlerList.AddHandler("OutputPlugins", (OutputPluginEventHandler)op.EventHandler); + break; + case "control": + // Control Plugins contain a command set that is parsed out into individual timers + // They also register a Web Response Handler to allow the web front end to call ExecuteControl + ControlPlugin cp = (ControlPlugin)plugin; + foreach (CommandData item in cp.Commands()) + m_pluginScheduler.AddTask(new PluginEventHandler(cp.ExecuteControl), + item, + item.FirstRun, + item.RepeatTimeSpan, + true); + + m_eventHandlerList.AddHandler(_name, new WebResponseEventHandler(cp.ExecuteControl)); + + //Some control plugins need to receive the sensor values from the input plugins. + //if the plugin says its implimented, set up to send on receive. + if (cp.ImplimentsEventHandler()) + m_eventHandlerList.AddHandler("OutputPlugins", (OutputPluginEventHandler)cp.EventHandler); + break; + default: + break; + } + } + } + } + } + catch (IOException) { throw; } + //catch (NullReferenceException) { Debug.Print("Null Reference exception error loading plugin: " + _name); } + return; + } + } } \ No newline at end of file diff --git a/Controller/Properties/AssemblyInfo.cs b/Controller/Properties/AssemblyInfo.cs index 387c373..928dd9a 100644 --- a/Controller/Properties/AssemblyInfo.cs +++ b/Controller/Properties/AssemblyInfo.cs @@ -7,10 +7,10 @@ // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("Controller")] -[assembly: AssemblyDescription("Netduino Plus Aquarium Controller")] +[assembly: AssemblyDescription("Netduino Plus Grow Controller")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Fishfornerds.com")] -[assembly: AssemblyProduct("Aquarium Controller")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Grow Controller")] [assembly: AssemblyCopyright("")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/Controller/config.js.example b/Controller/config.js.example index 638ad09..df1bbee 100644 --- a/Controller/config.js.example +++ b/Controller/config.js.example @@ -1,36 +1,59 @@ -{"config": { - "input": { - "Temperature": { - "enabled":"true", - "interval":[0,0,10] <- TimeSpan layout of Hour, Minute, Second - }, - "pH": { - "enabled":"true", - "interval":[0,0,10] <- TimeSpan layout of Hour, Minute, Second - } - }, - "output": { - "Thingspeak": { - "enabled":"true", - "write-api":"Thingspeak API Key" - }, - "Logfile": { - "enabled":"true", - "file-name":"\\SD\\log.txt" - }, - "AquariumStatus": { - "enabled":"true" - } - }, - "control": { - "Relays": { - "enabled":"true", - "relays": [ - {"id":0, "on":"07:30", "off":"17:00"}, - {"id":1, "on":"07:30", "off":"17:00"}, - {"id":2, "on":"01:00", "off":"02:00"}, - {"id":3, "on":"10:00", "off":"10:10"} - ] - } - } -}} \ No newline at end of file +var config={"config": { + "input": { + "Temperature": { + "enabled":"true", + "interval":[0,0,10] + }, + "pH": { + "enabled":"true", + "interval":[0,0,10] + }, + "ElectricalConductivity": { + "enabled":"true", + "ProbeType":2, + "interval":[0,0,10] + }, + "DHTSensor": { + "enabled": "true", + "interval": [0,0,10] + }, + "CO2": { + "enabled":"true", + "interval":[0,0,10] + } + }, + "output": { + "Thingspeak": { + "enabled": "true", + "channel": "", + "writeapi": "", + "readapi": "" + }, + "Logfile": { + "enabled":"true", + "filename":"\\SD\\log.txt" + }, + "AquariumStatus": { + "enabled":"true" + } + }, + "control": { + "Relays": { + "enabled":"true", + "relays": [ + { "name": "Vent", "type": "Timer", "id": 0, "DurationOn": [00, 05, 00], "DurationOff": [01, 00, 00], "Enable": "true" }, + { "name": "CO2", "type": "Range", "id": 1, "min": 1400, "max": 1500, "Inverted": "true", "PulseTime": 0, "PulseSpace": 0, "RangeMetric": "CO2", "Enable": "false" }, + { "name": "Water Pump", "type": "DailyTimer", "id": 2, "on": "00:00", "off": "23:59", "Enable": "false" }, + { "name": "Heater", "type": "Range", "id": 3, "min": 21.1, "max": 22.8, "Inverted": "true", "PulseTime": 0, "PulseSpace": 0, "RangeMetric": "AirTemperature", "Enable": "true" }, + { "name": "Water Heater", "type": "Range", "id": 4, "min": 18.3, "max": 19.4, "Inverted": "true", "PulseTime": 0, "PulseSpace": 0, "RangeMetric": "Temperature", "Enable": "true" }, + { "name": "Humidifier", "type": "Range", "id": 5, "min": 50, "max": 55, "Inverted": "true", "PulseTime": 0, "PulseSpace": 0, "RangeMetric": "Humidity", "Enable": "true" }, + { "name": "Fan", "type": "DailyTimer", "id": 6, "on": "10:00", "off": "10:10", "Enable": "false" }, + { "name": "Lights", "type": "DailyTimer", "id": 7, "on": "05:00", "off": "23:00", "Enable": "true" }, + { "name": "PH Down Pump", "type": "Range", "id": 8, "min": 5.5, "max": 6.5, "Inverted": "false", "PulseTime": 1000, "PulseSpace": 5, "RangeMetric": "pH", "Enable": "true" }, + { "name": "Flora Grow Pump", "type": "Range", "id": 9, "min": 1000, "max": 1500, "Inverted": "true", "PulseTime": 3000, "PulseSpace": 5, "RangeMetric": "TDS", "Enable": "true" }, + { "name": "Flora Micro Pump", "type": "Range", "id": 10, "min": 1000, "max": 1500, "Inverted": "true", "PulseTime":2000,"PulseSpace":5, "RangeMetric": "TDS", "Enable": "true" }, + { "name": "Flora Bloom Pump", "type": "Range", "id": 11, "min": 1000, "max": 1500, "Inverted": "true", "PulseTime":1000,"PulseSpace":5, "RangeMetric": "TDS", "Enable": "true" } + ] + } + } +}}; \ No newline at end of file diff --git a/Controller/fragments/body-end.htm b/Controller/fragments/body-end.htm index 89a01b6..afb8358 100644 --- a/Controller/fragments/body-end.htm +++ b/Controller/fragments/body-end.htm @@ -1 +1,5 @@ - \ No newline at end of file +$("#tabs").tabs(); +$("input:submit").button().click(function(){ + $.ajax({ + url:'/SaveConfig', + type:'POST', + dataType:'json', + data:{json:JSON.stringify(config)}, + success: function (data) + { + var obj = $.parseJSON(data); + //alert(obj); + // do something with result + } + }); +});}); + + + \ No newline at end of file diff --git a/Controller/fragments/header.htm b/Controller/fragments/header.htm index b7927dc..d6c8669 100644 --- a/Controller/fragments/header.htm +++ b/Controller/fragments/header.htm @@ -1 +1,10 @@ -Fish For Nerds - Aquarium Controller \ No newline at end of file + + + + + Garden For Nerds - Garden Controller + + + + + \ No newline at end of file diff --git a/NetduinoPlusTelnet/NetduinoPlusTelnet.sln b/NetduinoPlusTelnet/NetduinoPlusTelnet.sln new file mode 100644 index 0000000..4b6f767 --- /dev/null +++ b/NetduinoPlusTelnet/NetduinoPlusTelnet.sln @@ -0,0 +1,24 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetduinoPlusTelnet", "NetduinoPlusTelnet\NetduinoPlusTelnet.csproj", "{58DB2B34-17A3-448D-A0B5-A5FEBEAC6F53}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6BFC32C4-2172-4E91-B854-2CD5718B9D47}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {58DB2B34-17A3-448D-A0B5-A5FEBEAC6F53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {58DB2B34-17A3-448D-A0B5-A5FEBEAC6F53}.Debug|Any CPU.Build.0 = Debug|Any CPU + {58DB2B34-17A3-448D-A0B5-A5FEBEAC6F53}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {58DB2B34-17A3-448D-A0B5-A5FEBEAC6F53}.Release|Any CPU.ActiveCfg = Release|Any CPU + {58DB2B34-17A3-448D-A0B5-A5FEBEAC6F53}.Release|Any CPU.Build.0 = Release|Any CPU + {58DB2B34-17A3-448D-A0B5-A5FEBEAC6F53}.Release|Any CPU.Deploy.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/NetduinoPlusTelnet/NetduinoPlusTelnet/NetduinoPlusTelnet.csproj b/NetduinoPlusTelnet/NetduinoPlusTelnet/NetduinoPlusTelnet.csproj new file mode 100644 index 0000000..affe016 --- /dev/null +++ b/NetduinoPlusTelnet/NetduinoPlusTelnet/NetduinoPlusTelnet.csproj @@ -0,0 +1,49 @@ + + + + NetduinoPlusTelnet + Library + NetduinoPlusTelnet + {b69e3092-b931-443c-abe7-7e7b65f2a37f};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 9.0.21022 + 2.0 + {58DB2B34-17A3-448D-A0B5-A5FEBEAC6F53} + v4.2 + $(MSBuildExtensionsPath32)\Microsoft\.NET Micro Framework\ + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/NetduinoPlusTelnet/NetduinoPlusTelnet/Properties/AssemblyInfo.cs b/NetduinoPlusTelnet/NetduinoPlusTelnet/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..74be25f --- /dev/null +++ b/NetduinoPlusTelnet/NetduinoPlusTelnet/Properties/AssemblyInfo.cs @@ -0,0 +1,25 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("NetduinoPlusTelnet")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("NetduinoPlusTelnet")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2010")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/NetduinoPlusTelnet/NetduinoPlusTelnet/TelnetServer.cs b/NetduinoPlusTelnet/NetduinoPlusTelnet/TelnetServer.cs new file mode 100644 index 0000000..f642398 --- /dev/null +++ b/NetduinoPlusTelnet/NetduinoPlusTelnet/TelnetServer.cs @@ -0,0 +1,414 @@ +using System; +using System.Threading; +using Microsoft.SPOT; +using Microsoft.SPOT.Hardware; +using SecretLabs.NETMF.Hardware; +using SecretLabs.NETMF.Hardware.Netduino; +using System.Net.Sockets; +using System.Net; +using System.Text; +using Microsoft.SPOT.Net.NetworkInformation; +using System.IO.Ports; + +namespace NetduinoPlusTelnet +{ + public class TelnetServer + { + private int port; + private int connectionBuffers; + private int maximumConnections; + private ConnectionHandler[] connHandlers; + private int connTimeout; + private Thread connDispatch = null; + private Socket telnetSocket = null; + + /* Constructor for the TelnetServer class. */ + public TelnetServer(int timeout = 120, int listenPort = 23, int connBuffer = 1, int maxConn = 3) + { + // How many seconds to maintain an active, idle connection? + connTimeout = timeout; + + // What port? 23 is the Telnet default, but for security might want to set MUCH higher. + port = listenPort; + + // How many simultanious -NEW- connections will be handled? + connectionBuffers = connBuffer; + + // How many concurrent -ACTIVE- connections will be handled? + maximumConnections = maxConn; + } + + /* This method activates the Telnet Server. The object doesn't listen for connections until this is called. */ + public bool begin(bool sync = false) + { + + // Usual stuff... Set up the Socket object. Some line like this will probably be in 90% of Netduino+ projects. + telnetSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + // Ok... No flack about binding to all IP addresses. Trying to follow the KISS principle at least a little... + telnetSocket.Bind(new IPEndPoint(IPAddress.Any, port)); + + // Set up the listener with a buffer backlog value set to connectionBuffers. + telnetSocket.Listen(connectionBuffers); + + // Load the connHandlers array with references to null ConnectionHanddler objects. + connHandlers = new ConnectionHandler[maximumConnections]; + + // Construct the ConnectionHandler objects. + for (int i = 0; i < maximumConnections; i++) + { + connHandlers[i] = new ConnectionHandler(connTimeout); + } + + // Start up a thread to handle connection dispatching asynchronously, or don't if told not to. + if (sync) + { + _connectionDispatcher(); + } + else + { + connDispatch = new Thread(_connectionDispatcher); + connDispatch.Start(); + } + + // Maybe I'll throw in error checking some day, but for now... + return true; + } + + /* This method is actually a thread for accepting connections and dispatching them to the connection handler. */ + private void _connectionDispatcher() + { + // Dispatch connections forever. + while (true) + { + // Check the status of all handlers. + foreach (ConnectionHandler c in connHandlers) + { + if (c.isAvailable()) + { + // This handler is available. Listen for a new connection. + Socket newConnection = telnetSocket.Accept(); // Blocks thread until a connection is received. + + // Got a connection! Pass it to the connection handler. + c.acceptConnection(newConnection); + } + else + { + // No handlers are available - Yield to other threads. + Thread.Sleep(0); + } + } + } + } + } + + public class ConnectionHandler + { + private bool available; + private Socket activeConnection = null; + private Thread ispThread = null; + private int sessionTimeout; + private bool connectionOpen; + + // Set up some variables for incoming data here to avoid making unnecessary garbage. + private byte[] linesToSend; + private byte[] commandPrompt; + const int receiveBufSize = 80; // Probably not getting more than a line of text at at time. + const int commandBufSize = 255; + private byte[] receiveBuffer = new byte[receiveBufSize]; + private char[] commandBuffer = new char[commandBufSize]; + private int cmdBufWritePos; + private int readByteCount; + + public ConnectionHandler(int timeout = 300) + { + // This is a new object... It is definitely available. + available = true; + + // Set the connection timeout. + sessionTimeout = timeout; + } + + public bool acceptConnection(Socket incomingConnection) + { + // If we're already handling a connection, we can't do anything. + if (!available) return false; + + available = false; + + // Store the new connection. + activeConnection = incomingConnection; + + // Mark the current connection as open. + connectionOpen = true; + + // Start a thread for the interactive session processor to take care of the connection. + ispThread = new Thread(_sessionProcessor); + ispThread.Start(); + + return true; + } + + // Can this handler instance accept a new connection or is it in use? + public bool isAvailable() + { + return available; + } + + /* Method to become a thread for handling interactive sessions. */ + private void _sessionProcessor() + { + // Set up the start point on the command buffer to the beginning of it. + cmdBufWritePos = 0; + + // Prepare the Message Of The Day and the command prompt. "\n" means Line Feed, "\r" means Carrage Return. + linesToSend = Encoding.UTF8.GetBytes("Welcome to the Netduino Plus Telnet Server!\n\r\n\r"); + commandPrompt = Encoding.UTF8.GetBytes("Command> "); + + // Get the initial telnet data from the telnet client. + readByteCount = activeConnection.Receive(receiveBuffer); + + // Junk it for now.... I'll figure out what to do with this later. + for (int i = 0; i < readByteCount; i++) + { + receiveBuffer[i] = 0; + } + + // Print the MOTD and the command prompt. + activeConnection.Send(linesToSend, SocketFlags.None); + activeConnection.Send(commandPrompt, SocketFlags.None); + + // Loop will be ended via Thread return after the connection is closed. + while (connectionOpen) + { + // Wait for user input, but only until the timeout period is reached. + activeConnection.Poll((sessionTimeout * 1000000), SelectMode.SelectRead); + + // If the poll returned and nothing is available to read, it either timed out or the connection was closed. + if (activeConnection.Available == 0) break; + + // We Got some data... Do something with it. + do + { + // Read the data. + readByteCount = activeConnection.Receive(receiveBuffer, receiveBufSize, SocketFlags.None); + + // *** Data comes across as plain text here. No UTF-8 decode needed. + + // This method is only broken out for clarity of code. + if (!_processReceivedData()) + { + connectionOpen = false; // Drop out of the outer loop as well. + break; + } + + } while (activeConnection.Available > 0); + + } + + // Socket state is needs to be closed. + + activeConnection.Close(); + activeConnection = null; + available = true; + return; + + } + + /* This method appends the received data to the command buffer. */ + /* The command buffer is necessary in case the command comes in fragmented. */ + private bool _processReceivedData() + { + if ((receiveBuffer.Length) > (commandBufSize - cmdBufWritePos)) + { + // Command buffer will overflow... Clear the buffer and tell the user. + cmdBufWritePos = 0; + _sendReplyAndPrompt("Command line is too long!\r\n"); + return true; + } + + // "Copy" the characters from the receive buffer to the command buffer and check for command completion. + for (int i = 0; i < readByteCount; i++) + { + // Decide if a command is complete or needs attention. + switch (receiveBuffer[i]) + { + case 13: // Line feed. Skip it... Activate on carriage return. + break; + + case 10: // Carriage return. + if (!_processCommand(cmdBufWritePos)) return false; // False indicates connection close requested. + + // Command was handled - Reset the position so further entries land at the beginning. + cmdBufWritePos = 0; + break; + + case 9: // Tab key. + // Do nothing for Tab right now... + break; + + case 8: // Backspace key. + // Do nothing for Backspace right now... + break; + + default: // No action key received. Append the byte to the command buffer as a character. + commandBuffer[cmdBufWritePos] = Convert.ToChar(receiveBuffer[i]); + // Advance the command buffer write position. + cmdBufWritePos++; + break; + } + } + + return true; + } + + /* This method processes individual commands from the command buffer. */ + /* True indicates command processed successfully. False closes the connection. */ + private bool _processCommand(int commandLength) + { + // Grab the command and change it to a string for simplicity's sake. + String commandLine = new String(commandBuffer, 0, commandLength); + + // Split it up into command and arguments. + String[] command = commandLine.Split(' '); + + // Pick the command and execute it. + switch (command[0].ToLower()) + { + // All the hard work is done... This part pretty self-explanatory. + case "calibrate": + if (command.Length > 1) // This command takes 1 argument. + { + switch (command[1].ToLower()) // What do we do with the LED? + { + case "ph": + _sendReply("Begining PH Calibration. Connected to PH Stamp. Press ^ to Exit.\r\n"); + _sendReply("1. Type C to enter continues mode.\r\n"); + _sendReply("2. Place probe in PH 7 for 1-2 mins then enter S.\r\n"); + _sendReply("3. Place probe in PH 4 for 1-2 mins then enter F.\r\n"); + _sendReply("4. Place probe in PH 10 for 1-2 mins then enter T.\r\n"); + _sendReply("5. Press E to end.\r\n"); + Calibrate(Serial.COM2); + _sendReplyAndPrompt("Completed calibration for PH.\r\n"); + break; + case "ec": + _sendReply("Begining EC Calibration. Connected to EC Stamp. Press ^ to Exit.\r\n"); + _sendReply("1. Type C to enter continues mode..\r\n"); + _sendReply("2. Set Probe Type - P,1 = K0.1 | P,2 = K1.0, P,3 = K10.0.\r\n"); + _sendReply("3. Dry Calibration - Enter Z0.\r\n"); + _sendReply("4. Place probe in High(40,000) solution 3-5 minutes and enter Z40.\r\n"); + _sendReply("5. Place probe in Low(10,500) solution 3-5 minutes and enter Z10.\r\n"); + Calibrate(Serial.COM1); + _sendReplyAndPrompt("Completed calibration for EC.\r\n"); + break; + default: + _sendReplyAndPrompt("Please specify 'ph' or 'ec'.\r\n"); + break; + } + } + else // Not enough arguments! + { + _sendReplyAndPrompt("Please specify 'ph' or 'ec'.\r\n"); + } + break; + case "reboot": + _sendReply("Device is going down for a reboot.\r\n"); + Thread.Sleep(2000); + PowerState.RebootDevice(false); //Reboot Device + break; + case "exit": // Got to have a way out, right? + return false; + default: + StringBuilder sb = new StringBuilder(); + sb.AppendLine("Available commands are:"); + sb.AppendLine("calibrate - Calibrate ph or ec"); + sb.AppendLine("reboot - Reboot the device."); + sb.AppendLine("exit - End the connection."); + _sendReplyAndPrompt(sb.ToString()); + break; + } + return true; + } + + private void _sendReplyAndPrompt(String strToSend) + { + Byte[] responseBytes = Encoding.UTF8.GetBytes(strToSend + "Command> "); + activeConnection.Send(responseBytes, SocketFlags.None); + } + private void _sendReply(String strToSend) + { + Byte[] responseBytes = Encoding.UTF8.GetBytes(strToSend); + activeConnection.Send(responseBytes, SocketFlags.None); + } + + /// + /// Relays data between a serial port and the telnet session. + /// This allows access from telnet to the serial + /// port of the EC and PH moduled directly. + /// + /// String for the serial port you want to access. + private void Calibrate(String SerialPortName) + { + SerialPort sp = new SerialPort(SerialPortName, 38400, Parity.None, 8, StopBits.One); + try + { + byte ExitCommand = 94; //^ Key + bool Continue = true; + int m_LoopCount = 0; + sp.ReadTimeout = 4000; + sp.WriteTimeout = 4000; + sp.Open(); + do + { + m_LoopCount += 1; //Incriment loop count for inactivity. + + if (activeConnection.Available > 0) + { + byte[] t_buffer = new byte[activeConnection.Available]; + activeConnection.Receive(t_buffer); + if (t_buffer[0] == ExitCommand) //exit if ^ is pressed + { + Continue = false; + break; + } + else + { + if (t_buffer[t_buffer.Length - 2] == 13 && t_buffer[t_buffer.Length - 1] == 10) //When sending data to the stamp replace crlf with return. + { + m_LoopCount = 0; //Reset Loop Count + sp.Write(t_buffer, 0, t_buffer.Length - 1); //Send message to the serial port + } + else + { + m_LoopCount = 0; //Reset Loop Count + sp.Write(t_buffer, 0, t_buffer.Length); //Send message to the serial port + } + sp.Flush(); + } + t_buffer = null; + } + if (sp.BytesToRead > 0) + { + byte[] ph_cmd = new byte[sp.BytesToRead]; + sp.Read(ph_cmd, 0, ph_cmd.Length); //Read from serial port + activeConnection.Send(ph_cmd, SocketFlags.None); //Write what was read to telnet session + m_LoopCount = 0; //Reset Loop Count + ph_cmd = null; + } + Thread.Sleep(100); + } while (Continue && m_LoopCount < 60000); //Time out after 1 minute of inactivity. + } + catch (Exception e) + { + _sendReply("Error accessing serial port: \r\n" + e.Message + "\r\nIt may be in use. Try again later.\r\n"); + } + finally + { + //close the serial port if its opened and Dispose it. + if (sp.IsOpen) + sp.Close(); + sp.Dispose(); + } + } + } +} diff --git a/Pin Config.txt b/Pin Config.txt new file mode 100644 index 0000000..5012518 --- /dev/null +++ b/Pin Config.txt @@ -0,0 +1,67 @@ +NetDuino Pinout +00 - EC TX +01 - EC RX +02 - PH TX +03 - PH RX +04 - Relay +05 - Relay +06 - Relay +07 - Relay +08 - Relay +09 - Relay +10 - Relay +11 - Relay +12 - +13 - +A0 - WT Sensor Pin (Orange) +A1 - +Pump Pinout +Blue Pair - 5v +Orange Pair - 9v +A2 - Relay (PH Down Pump) (Green) +A3 - Relay (Flora Grow Pump) (Green\White) +A4 - Relay (Flora Micro Pump) (Brown\White) +A5 - Relay (Flora Bloom Pump) (Brown) +SD - To Arduino TX (WhiteGreen) +SC - To Arduino RX + + (Green) + +Air Box Pinout + +Blue Pair - 5v + +Orange Pair - 9v + +Green - Arduino RX + +WhiteGreen - Arduino TX + +Arduino Pinout (Inside of Air Box) +RX - Netduino +TX - Netduino +D2 - DHT11 Sensor Pin +D3 - CO2 Sensor Bool Pin + +A7 - C02 Sensor Sensor Pin + + +Water Sensor Breakout Box + +BW - Water Temp Sensor GND + +B - Water Temp Sensor VCC + +O - Water Temp Sensor Signal + + + +ThingSpeak IDs: +1 - Water Temp +2 - pH +3 - CO2 +4 - Microsiemens +5 - PPM +6 - Salinity +7 - Air Temperature +8 - Humidity \ No newline at end of file diff --git a/Plugin-AquariumStatus/AquariumStatus.cs b/Plugin-AquariumStatus/AquariumStatus.cs index 3d263ed..b537610 100644 --- a/Plugin-AquariumStatus/AquariumStatus.cs +++ b/Plugin-AquariumStatus/AquariumStatus.cs @@ -1,57 +1,53 @@ -using System; -using System.Collections; -using System.IO; -using System.Text; -using Controller; -using Microsoft.SPOT; - -namespace Plugins -{ - /// - /// Output plugin that provides current tank conditions to the web front end via JS - /// - /// Will eventually write out to attached LCD, but that's still VERY WIP - public class AquariumStatus : OutputPlugin - { - ~AquariumStatus() { Dispose(); } - public override void Dispose() { } - - public AquariumStatus() { } - public AquariumStatus(object _config) { } - - private const string m_statusFileName = @"\SD\status.js"; - - public override void EventHandler(object _sender, IPluginData _data) - { - // Load status.js and update necessary variables - Hashtable status = (Hashtable)JSON.JsonDecodeFromFile(m_statusFileName); - switch (_data.DataType()) - { - case ThingSpeakFields.pH: - status["pH"] = _data.GetValue().ToString("F"); - break; - case ThingSpeakFields.Temperature: - status["Temperature"] = _data.GetValue().ToString("F"); - break; - default: - break; - } - status["time"] = DateTime.Now.ToString("s"); - - foreach (DictionaryEntry item in status) - { - Debug.Print(item.Key.ToString() + "=" + item.Value.ToString()); - } - - // write status.js back down to fs, including the var declaration - string statusString = JSON.JsonEncode(status); - statusString = "var aq=" + statusString; - byte[] statusBytes = Encoding.UTF8.GetBytes(statusString); - using (FileStream fs = new FileStream(m_statusFileName, FileMode.Truncate)) - { - fs.Write(statusBytes, 0, statusBytes.Length); - } - - } - } -} +using System; +using System.Collections; +using System.IO; +using System.Text; +using Controller; +using Microsoft.SPOT; + +namespace Plugins +{ + /// + /// Output plugin that provides current tank conditions to the web front end via JS + /// + /// Will eventually write out to attached LCD, but that's still VERY WIP + public class AquariumStatus : OutputPlugin + { + ~AquariumStatus() { Dispose(); } + public override void Dispose() { } + public AquariumStatus() { } + public AquariumStatus(object _config) { } + + private const string m_statusFileName = @"\SD\status.js"; + + public override void EventHandler(object _sender, IPluginData _data) + { + try + { + // Load status.js and update necessary variables + Hashtable status = (Hashtable)JSON.JsonDecodeFromVar(m_statusFileName); + + foreach (PluginData _pd in _data.GetData()) + { + if (_pd.LastReadSuccess) + status[_pd.Name] = _pd.Value.ToString("F"); + } + status["time"] = DateTime.Now.ToString("s"); + + // write status.js back down to fs, including the var declaration + string statusString = JSON.JsonEncode(status); + statusString = "var aq=" + statusString; + byte[] statusBytes = Encoding.UTF8.GetBytes(statusString); + using (FileStream fs = new FileStream(m_statusFileName, FileMode.Create)) + { + fs.Write(statusBytes, 0, statusBytes.Length); + } + } + catch (Exception e) + { + Debug.Print(e.Message); + } + + } + } +} diff --git a/Plugin-AquariumStatus/AquariumStatus.htm b/Plugin-AquariumStatus/AquariumStatus.htm index 2522196..dfb8bdf 100644 --- a/Plugin-AquariumStatus/AquariumStatus.htm +++ b/Plugin-AquariumStatus/AquariumStatus.htm @@ -1 +1,6 @@ -
Aquarium Status

Enable Web front end status updates and send updates to the LCD

\ No newline at end of file +
+ Aquarium Status + + +

Enable Web front end status updates and send updates to the LCD

+
\ No newline at end of file diff --git a/Plugin-AquariumStatus/AquariumStatus.js b/Plugin-AquariumStatus/AquariumStatus.js index f679f69..993e2b9 100644 --- a/Plugin-AquariumStatus/AquariumStatus.js +++ b/Plugin-AquariumStatus/AquariumStatus.js @@ -1,4 +1,6 @@ var AquariumStatusInit = function () { + var ThingSpeakChannel = config.config.output.Thingspeak.channel; + var ThingSpeakKey = config.config.output.Thingspeak.readapi; var pluginEnabled = (config.config.output.AquariumStatus.enabled === 'true'); var asEnable = $('#PAQE'); asEnable.attr('checked', pluginEnabled); @@ -8,6 +10,12 @@ }); asEnable.button({ label: (pluginEnabled ? "Enabled" : "Disabled") }).button('refresh'); $('#status-time').text(aq.time); - $('#status-temperature').text(aq.Temperature); - $('#status-ph').text(aq.pH); + $('#status-Temperature').html('' + aq.Temperature + ''); + $('#status-pH').html('' + aq.pH + ''); + $('#status-Microsiemens').html('' + aq.Microsiemens + ''); + $('#status-TDS').html('' + aq.TDS + ''); + $('#status-Salinity').html('' + aq.Salinity + ''); + $('#status-CO2').html('' + aq.CO2 + ''); + $('#status-AirTemperature').html('' + aq.AirTemperature + ''); + $('#status-Humidity').html('' + aq.Humidity + ''); }; \ No newline at end of file diff --git a/Plugin-AquariumStatus/AquariumStatus.min.js b/Plugin-AquariumStatus/AquariumStatus.min.js deleted file mode 100644 index fbb85c7..0000000 --- a/Plugin-AquariumStatus/AquariumStatus.min.js +++ /dev/null @@ -1 +0,0 @@ -var AquariumStatusInit=function(){var a=config.config.input.AquariumStatus.enabled==="true";var b=$("#PAQE");b.attr("checked",a);b.click(function(){$(this).button("option","label",this.checked?"Enabled":"Disabled");config.config.input.AquariumStatus.enabled=this.checked?"true":"false"});b.button({label:a?"Enabled":"Disabled"}).button("refresh")} \ No newline at end of file diff --git a/Plugin-AquariumStatus/Plugin-AquariumStatus.csproj b/Plugin-AquariumStatus/Plugin-AquariumStatus.csproj index ddaa4dc..d5adc1e 100644 --- a/Plugin-AquariumStatus/Plugin-AquariumStatus.csproj +++ b/Plugin-AquariumStatus/Plugin-AquariumStatus.csproj @@ -1,56 +1,68 @@ - - - - AquariumStatus - Library - Plugins - {b69e3092-b931-443c-abe7-7e7b65f2a37f};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 9.0.21022 - 2.0 - {F0F2B76C-B9A5-4BA8-B551-5217295EF377} - v4.2 - $(MSBuildExtensionsPath32)\Microsoft\.NET Micro Framework\ - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - - {117848BD-213C-4AE4-8F58-D27F14DAA534} - Controller - - - + + + + AquariumStatus + Library + Plugins + {b69e3092-b931-443c-abe7-7e7b65f2a37f};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 9.0.21022 + 2.0 + {F0F2B76C-B9A5-4BA8-B551-5217295EF377} + v4.2 + $(MSBuildExtensionsPath32)\Microsoft\.NET Micro Framework\ + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + {117848BD-213C-4AE4-8F58-D27F14DAA534} + Controller + + + + + + copy $(ProjectDir)status.js g:\ -copy $(TargetDir)le\$(TargetName).pe g:\plugins -copy $(ProjectDir)$(TargetName).htm g:\plugins - + +copy $(TargetDir)le\$(TargetName).pe g:\plugins\ + +copy $(ProjectDir)$(TargetName).htm g:\plugins\ +copy $(ProjectDir)$(TargetName).js g:\files\$(TargetName).min.js +copy $(ProjectDir)banner.jpg g:\files\ +copy $(ProjectDir)jquery.css g:\files\ +copy $(ProjectDir)ui.css g:\files\ +copy $(ProjectDir)images\* g:\files\images\ + \ No newline at end of file diff --git a/Plugin-AquariumStatus/banner.jpg b/Plugin-AquariumStatus/banner.jpg new file mode 100644 index 0000000..1b62045 Binary files /dev/null and b/Plugin-AquariumStatus/banner.jpg differ diff --git a/Plugin-AquariumStatus/images/ui-bg_glass_45_0078ae_1x400.png b/Plugin-AquariumStatus/images/ui-bg_glass_45_0078ae_1x400.png new file mode 100644 index 0000000..3dac650 Binary files /dev/null and b/Plugin-AquariumStatus/images/ui-bg_glass_45_0078ae_1x400.png differ diff --git a/Plugin-AquariumStatus/images/ui-bg_glass_75_79c9ec_1x400.png b/Plugin-AquariumStatus/images/ui-bg_glass_75_79c9ec_1x400.png new file mode 100644 index 0000000..d384e42 Binary files /dev/null and b/Plugin-AquariumStatus/images/ui-bg_glass_75_79c9ec_1x400.png differ diff --git a/Plugin-AquariumStatus/images/ui-bg_gloss-wave_50_6eac2c_500x100.png b/Plugin-AquariumStatus/images/ui-bg_gloss-wave_50_6eac2c_500x100.png new file mode 100644 index 0000000..76dac56 Binary files /dev/null and b/Plugin-AquariumStatus/images/ui-bg_gloss-wave_50_6eac2c_500x100.png differ diff --git a/Plugin-AquariumStatus/images/ui-bg_gloss-wave_75_2191c0_500x100.png b/Plugin-AquariumStatus/images/ui-bg_gloss-wave_75_2191c0_500x100.png new file mode 100644 index 0000000..eeacf69 Binary files /dev/null and b/Plugin-AquariumStatus/images/ui-bg_gloss-wave_75_2191c0_500x100.png differ diff --git a/Plugin-AquariumStatus/images/ui-bg_inset-hard_100_fcfdfd_1x100.png b/Plugin-AquariumStatus/images/ui-bg_inset-hard_100_fcfdfd_1x100.png new file mode 100644 index 0000000..38c3833 Binary files /dev/null and b/Plugin-AquariumStatus/images/ui-bg_inset-hard_100_fcfdfd_1x100.png differ diff --git a/Plugin-AquariumStatus/jquery.css b/Plugin-AquariumStatus/jquery.css new file mode 100644 index 0000000..91bb97c --- /dev/null +++ b/Plugin-AquariumStatus/jquery.css @@ -0,0 +1,565 @@ +/* + * jQuery UI CSS Framework 1.8.18 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Theming/API + */ + +/* Layout helpers +----------------------------------*/ +.ui-helper-hidden { display: none; } +.ui-helper-hidden-accessible { position: absolute !important; clip: rect(1px 1px 1px 1px); clip: rect(1px,1px,1px,1px); } +.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; } +.ui-helper-clearfix:before, .ui-helper-clearfix:after { content: ""; display: table; } +.ui-helper-clearfix:after { clear: both; } +.ui-helper-clearfix { zoom: 1; } +.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); } + + +/* Interaction Cues +----------------------------------*/ +.ui-state-disabled { cursor: default !important; } + + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; } + + +/* Misc visuals +----------------------------------*/ + +/* Overlays */ +.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } + + +/* + * jQuery UI CSS Framework 1.8.18 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Theming/API + * + * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana,Arial,sans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=5px&bgColorHeader=2191c0&bgTextureHeader=12_gloss_wave.png&bgImgOpacityHeader=75&borderColorHeader=4297d7&fcHeader=eaf5f7&iconColorHeader=d8e7f3&bgColorContent=fcfdfd&bgTextureContent=06_inset_hard.png&bgImgOpacityContent=100&borderColorContent=a6c9e2&fcContent=222222&iconColorContent=0078ae&bgColorDefault=0078ae&bgTextureDefault=02_glass.png&bgImgOpacityDefault=45&borderColorDefault=77d5f7&fcDefault=ffffff&iconColorDefault=e0fdff&bgColorHover=79c9ec&bgTextureHover=02_glass.png&bgImgOpacityHover=75&borderColorHover=448dae&fcHover=026890&iconColorHover=056b93&bgColorActive=6eac2c&bgTextureActive=12_gloss_wave.png&bgImgOpacityActive=50&borderColorActive=acdd4a&fcActive=ffffff&iconColorActive=f5e175&bgColorHighlight=f8da4e&bgTextureHighlight=02_glass.png&bgImgOpacityHighlight=55&borderColorHighlight=fcd113&fcHighlight=915608&iconColorHighlight=f7a50d&bgColorError=e14f1c&bgTextureError=12_gloss_wave.png&bgImgOpacityError=45&borderColorError=cd0a0a&fcError=ffffff&iconColorError=fcd113&bgColorOverlay=aaaaaa&bgTextureOverlay=01_flat.png&bgImgOpacityOverlay=75&opacityOverlay=30&bgColorShadow=999999&bgTextureShadow=01_flat.png&bgImgOpacityShadow=55&opacityShadow=45&thicknessShadow=0px&offsetTopShadow=5px&offsetLeftShadow=5px&cornerRadiusShadow=5px + */ + + +/* Component containers +----------------------------------*/ +.ui-widget { font-family: Verdana,Arial,sans-serif; font-size: 1.1em; } +.ui-widget .ui-widget { font-size: 1em; } +.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Verdana,Arial,sans-serif; font-size: 1em; } +.ui-widget-content { border: 1px solid #a6c9e2; background: #fcfdfd url(images/ui-bg_inset-hard_100_fcfdfd_1x100.png) 50% bottom repeat-x; color: #222222; } +.ui-widget-content a { color: #222222; } +.ui-widget-header { border: 1px solid #4297d7; background: #2191c0 url(images/ui-bg_gloss-wave_75_2191c0_500x100.png) 50% 50% repeat-x; color: #eaf5f7; font-weight: bold; } +.ui-widget-header a { color: #eaf5f7; } + +/* Interaction states +----------------------------------*/ +.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #77d5f7; background: #0078ae url(images/ui-bg_glass_45_0078ae_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #ffffff; } +.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #ffffff; text-decoration: none; } +.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #448dae; background: #79c9ec url(images/ui-bg_glass_75_79c9ec_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #026890; } +.ui-state-hover a, .ui-state-hover a:hover { color: #026890; text-decoration: none; } +.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #acdd4a; background: #6eac2c url(images/ui-bg_gloss-wave_50_6eac2c_500x100.png) 50% 50% repeat-x; font-weight: normal; color: #ffffff; } +.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #ffffff; text-decoration: none; } +.ui-widget :active { outline: none; } + +/* Interaction Cues +----------------------------------*/ +.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #fcd113; background: #f8da4e url(images/ui-bg_glass_55_f8da4e_1x400.png) 50% 50% repeat-x; color: #915608; } +.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #915608; } +.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a; background: #e14f1c url(images/ui-bg_gloss-wave_45_e14f1c_500x100.png) 50% top repeat-x; color: #ffffff; } +.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #ffffff; } +.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #ffffff; } +.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; } +.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; } +.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; } + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_0078ae_256x240.png); } +.ui-widget-content .ui-icon {background-image: url(images/ui-icons_0078ae_256x240.png); } +.ui-widget-header .ui-icon {background-image: url(images/ui-icons_d8e7f3_256x240.png); } +.ui-state-default .ui-icon { background-image: url(images/ui-icons_e0fdff_256x240.png); } +.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_056b93_256x240.png); } +.ui-state-active .ui-icon {background-image: url(images/ui-icons_f5e175_256x240.png); } +.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_f7a50d_256x240.png); } +.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_fcd113_256x240.png); } + +/* positioning */ +.ui-icon-carat-1-n { background-position: 0 0; } +.ui-icon-carat-1-ne { background-position: -16px 0; } +.ui-icon-carat-1-e { background-position: -32px 0; } +.ui-icon-carat-1-se { background-position: -48px 0; } +.ui-icon-carat-1-s { background-position: -64px 0; } +.ui-icon-carat-1-sw { background-position: -80px 0; } +.ui-icon-carat-1-w { background-position: -96px 0; } +.ui-icon-carat-1-nw { background-position: -112px 0; } +.ui-icon-carat-2-n-s { background-position: -128px 0; } +.ui-icon-carat-2-e-w { background-position: -144px 0; } +.ui-icon-triangle-1-n { background-position: 0 -16px; } +.ui-icon-triangle-1-ne { background-position: -16px -16px; } +.ui-icon-triangle-1-e { background-position: -32px -16px; } +.ui-icon-triangle-1-se { background-position: -48px -16px; } +.ui-icon-triangle-1-s { background-position: -64px -16px; } +.ui-icon-triangle-1-sw { background-position: -80px -16px; } +.ui-icon-triangle-1-w { background-position: -96px -16px; } +.ui-icon-triangle-1-nw { background-position: -112px -16px; } +.ui-icon-triangle-2-n-s { background-position: -128px -16px; } +.ui-icon-triangle-2-e-w { background-position: -144px -16px; } +.ui-icon-arrow-1-n { background-position: 0 -32px; } +.ui-icon-arrow-1-ne { background-position: -16px -32px; } +.ui-icon-arrow-1-e { background-position: -32px -32px; } +.ui-icon-arrow-1-se { background-position: -48px -32px; } +.ui-icon-arrow-1-s { background-position: -64px -32px; } +.ui-icon-arrow-1-sw { background-position: -80px -32px; } +.ui-icon-arrow-1-w { background-position: -96px -32px; } +.ui-icon-arrow-1-nw { background-position: -112px -32px; } +.ui-icon-arrow-2-n-s { background-position: -128px -32px; } +.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } +.ui-icon-arrow-2-e-w { background-position: -160px -32px; } +.ui-icon-arrow-2-se-nw { background-position: -176px -32px; } +.ui-icon-arrowstop-1-n { background-position: -192px -32px; } +.ui-icon-arrowstop-1-e { background-position: -208px -32px; } +.ui-icon-arrowstop-1-s { background-position: -224px -32px; } +.ui-icon-arrowstop-1-w { background-position: -240px -32px; } +.ui-icon-arrowthick-1-n { background-position: 0 -48px; } +.ui-icon-arrowthick-1-ne { background-position: -16px -48px; } +.ui-icon-arrowthick-1-e { background-position: -32px -48px; } +.ui-icon-arrowthick-1-se { background-position: -48px -48px; } +.ui-icon-arrowthick-1-s { background-position: -64px -48px; } +.ui-icon-arrowthick-1-sw { background-position: -80px -48px; } +.ui-icon-arrowthick-1-w { background-position: -96px -48px; } +.ui-icon-arrowthick-1-nw { background-position: -112px -48px; } +.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } +.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } +.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } +.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } +.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } +.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } +.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } +.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } +.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } +.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } +.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } +.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } +.ui-icon-arrowreturn-1-w { background-position: -64px -64px; } +.ui-icon-arrowreturn-1-n { background-position: -80px -64px; } +.ui-icon-arrowreturn-1-e { background-position: -96px -64px; } +.ui-icon-arrowreturn-1-s { background-position: -112px -64px; } +.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } +.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } +.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } +.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } +.ui-icon-arrow-4 { background-position: 0 -80px; } +.ui-icon-arrow-4-diag { background-position: -16px -80px; } +.ui-icon-extlink { background-position: -32px -80px; } +.ui-icon-newwin { background-position: -48px -80px; } +.ui-icon-refresh { background-position: -64px -80px; } +.ui-icon-shuffle { background-position: -80px -80px; } +.ui-icon-transfer-e-w { background-position: -96px -80px; } +.ui-icon-transferthick-e-w { background-position: -112px -80px; } +.ui-icon-folder-collapsed { background-position: 0 -96px; } +.ui-icon-folder-open { background-position: -16px -96px; } +.ui-icon-document { background-position: -32px -96px; } +.ui-icon-document-b { background-position: -48px -96px; } +.ui-icon-note { background-position: -64px -96px; } +.ui-icon-mail-closed { background-position: -80px -96px; } +.ui-icon-mail-open { background-position: -96px -96px; } +.ui-icon-suitcase { background-position: -112px -96px; } +.ui-icon-comment { background-position: -128px -96px; } +.ui-icon-person { background-position: -144px -96px; } +.ui-icon-print { background-position: -160px -96px; } +.ui-icon-trash { background-position: -176px -96px; } +.ui-icon-locked { background-position: -192px -96px; } +.ui-icon-unlocked { background-position: -208px -96px; } +.ui-icon-bookmark { background-position: -224px -96px; } +.ui-icon-tag { background-position: -240px -96px; } +.ui-icon-home { background-position: 0 -112px; } +.ui-icon-flag { background-position: -16px -112px; } +.ui-icon-calendar { background-position: -32px -112px; } +.ui-icon-cart { background-position: -48px -112px; } +.ui-icon-pencil { background-position: -64px -112px; } +.ui-icon-clock { background-position: -80px -112px; } +.ui-icon-disk { background-position: -96px -112px; } +.ui-icon-calculator { background-position: -112px -112px; } +.ui-icon-zoomin { background-position: -128px -112px; } +.ui-icon-zoomout { background-position: -144px -112px; } +.ui-icon-search { background-position: -160px -112px; } +.ui-icon-wrench { background-position: -176px -112px; } +.ui-icon-gear { background-position: -192px -112px; } +.ui-icon-heart { background-position: -208px -112px; } +.ui-icon-star { background-position: -224px -112px; } +.ui-icon-link { background-position: -240px -112px; } +.ui-icon-cancel { background-position: 0 -128px; } +.ui-icon-plus { background-position: -16px -128px; } +.ui-icon-plusthick { background-position: -32px -128px; } +.ui-icon-minus { background-position: -48px -128px; } +.ui-icon-minusthick { background-position: -64px -128px; } +.ui-icon-close { background-position: -80px -128px; } +.ui-icon-closethick { background-position: -96px -128px; } +.ui-icon-key { background-position: -112px -128px; } +.ui-icon-lightbulb { background-position: -128px -128px; } +.ui-icon-scissors { background-position: -144px -128px; } +.ui-icon-clipboard { background-position: -160px -128px; } +.ui-icon-copy { background-position: -176px -128px; } +.ui-icon-contact { background-position: -192px -128px; } +.ui-icon-image { background-position: -208px -128px; } +.ui-icon-video { background-position: -224px -128px; } +.ui-icon-script { background-position: -240px -128px; } +.ui-icon-alert { background-position: 0 -144px; } +.ui-icon-info { background-position: -16px -144px; } +.ui-icon-notice { background-position: -32px -144px; } +.ui-icon-help { background-position: -48px -144px; } +.ui-icon-check { background-position: -64px -144px; } +.ui-icon-bullet { background-position: -80px -144px; } +.ui-icon-radio-off { background-position: -96px -144px; } +.ui-icon-radio-on { background-position: -112px -144px; } +.ui-icon-pin-w { background-position: -128px -144px; } +.ui-icon-pin-s { background-position: -144px -144px; } +.ui-icon-play { background-position: 0 -160px; } +.ui-icon-pause { background-position: -16px -160px; } +.ui-icon-seek-next { background-position: -32px -160px; } +.ui-icon-seek-prev { background-position: -48px -160px; } +.ui-icon-seek-end { background-position: -64px -160px; } +.ui-icon-seek-start { background-position: -80px -160px; } +/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */ +.ui-icon-seek-first { background-position: -80px -160px; } +.ui-icon-stop { background-position: -96px -160px; } +.ui-icon-eject { background-position: -112px -160px; } +.ui-icon-volume-off { background-position: -128px -160px; } +.ui-icon-volume-on { background-position: -144px -160px; } +.ui-icon-power { background-position: 0 -176px; } +.ui-icon-signal-diag { background-position: -16px -176px; } +.ui-icon-signal { background-position: -32px -176px; } +.ui-icon-battery-0 { background-position: -48px -176px; } +.ui-icon-battery-1 { background-position: -64px -176px; } +.ui-icon-battery-2 { background-position: -80px -176px; } +.ui-icon-battery-3 { background-position: -96px -176px; } +.ui-icon-circle-plus { background-position: 0 -192px; } +.ui-icon-circle-minus { background-position: -16px -192px; } +.ui-icon-circle-close { background-position: -32px -192px; } +.ui-icon-circle-triangle-e { background-position: -48px -192px; } +.ui-icon-circle-triangle-s { background-position: -64px -192px; } +.ui-icon-circle-triangle-w { background-position: -80px -192px; } +.ui-icon-circle-triangle-n { background-position: -96px -192px; } +.ui-icon-circle-arrow-e { background-position: -112px -192px; } +.ui-icon-circle-arrow-s { background-position: -128px -192px; } +.ui-icon-circle-arrow-w { background-position: -144px -192px; } +.ui-icon-circle-arrow-n { background-position: -160px -192px; } +.ui-icon-circle-zoomin { background-position: -176px -192px; } +.ui-icon-circle-zoomout { background-position: -192px -192px; } +.ui-icon-circle-check { background-position: -208px -192px; } +.ui-icon-circlesmall-plus { background-position: 0 -208px; } +.ui-icon-circlesmall-minus { background-position: -16px -208px; } +.ui-icon-circlesmall-close { background-position: -32px -208px; } +.ui-icon-squaresmall-plus { background-position: -48px -208px; } +.ui-icon-squaresmall-minus { background-position: -64px -208px; } +.ui-icon-squaresmall-close { background-position: -80px -208px; } +.ui-icon-grip-dotted-vertical { background-position: 0 -224px; } +.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } +.ui-icon-grip-solid-vertical { background-position: -32px -224px; } +.ui-icon-grip-solid-horizontal { background-position: -48px -224px; } +.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } +.ui-icon-grip-diagonal-se { background-position: -80px -224px; } + + +/* Misc visuals +----------------------------------*/ + +/* Corner radius */ +.ui-corner-all, .ui-corner-top, .ui-corner-left, .ui-corner-tl { -moz-border-radius-topleft: 5px; -webkit-border-top-left-radius: 5px; -khtml-border-top-left-radius: 5px; border-top-left-radius: 5px; } +.ui-corner-all, .ui-corner-top, .ui-corner-right, .ui-corner-tr { -moz-border-radius-topright: 5px; -webkit-border-top-right-radius: 5px; -khtml-border-top-right-radius: 5px; border-top-right-radius: 5px; } +.ui-corner-all, .ui-corner-bottom, .ui-corner-left, .ui-corner-bl { -moz-border-radius-bottomleft: 5px; -webkit-border-bottom-left-radius: 5px; -khtml-border-bottom-left-radius: 5px; border-bottom-left-radius: 5px; } +.ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-br { -moz-border-radius-bottomright: 5px; -webkit-border-bottom-right-radius: 5px; -khtml-border-bottom-right-radius: 5px; border-bottom-right-radius: 5px; } + +/* Overlays */ +.ui-widget-overlay { background: #aaaaaa url(images/ui-bg_flat_75_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); } +.ui-widget-shadow { margin: 5px 0 0 5px; padding: 0px; background: #999999 url(images/ui-bg_flat_55_999999_40x100.png) 50% 50% repeat-x; opacity: .45;filter:Alpha(Opacity=45); -moz-border-radius: 5px; -khtml-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; }/* + * jQuery UI Resizable 1.8.18 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Resizable#theming + */ +.ui-resizable { position: relative;} +.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block; } +.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; } +.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; } +.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; } +.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; } +.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; } +.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; } +.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; } +.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; } +.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/* + * jQuery UI Selectable 1.8.18 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Selectable#theming + */ +.ui-selectable-helper { position: absolute; z-index: 100; border:1px dotted black; } +/* + * jQuery UI Accordion 1.8.18 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Accordion#theming + */ +/* IE/Win - Fix animation bug - #4615 */ +.ui-accordion { width: 100%; } +.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; } +.ui-accordion .ui-accordion-li-fix { display: inline; } +.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; } +.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em .7em; } +.ui-accordion-icons .ui-accordion-header a { padding-left: 2.2em; } +.ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; } +.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; zoom: 1; } +.ui-accordion .ui-accordion-content-active { display: block; } +/* + * jQuery UI Autocomplete 1.8.18 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Autocomplete#theming + */ +.ui-autocomplete { position: absolute; cursor: default; } + +/* workarounds */ +* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */ + +/* + * jQuery UI Menu 1.8.18 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Menu#theming + */ +.ui-menu { + list-style:none; + padding: 2px; + margin: 0; + display:block; + float: left; +} +.ui-menu .ui-menu { + margin-top: -3px; +} +.ui-menu .ui-menu-item { + margin:0; + padding: 0; + zoom: 1; + float: left; + clear: left; + width: 100%; +} +.ui-menu .ui-menu-item a { + text-decoration:none; + display:block; + padding:.2em .4em; + line-height:1.5; + zoom:1; +} +.ui-menu .ui-menu-item a.ui-state-hover, +.ui-menu .ui-menu-item a.ui-state-active { + font-weight: normal; + margin: -1px; +} +/* + * jQuery UI Button 1.8.18 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Button#theming + */ +.ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: hidden; *overflow: visible; } /* the overflow property removes extra width in IE */ +.ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */ +button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */ +.ui-button-icons-only { width: 3.4em; } +button.ui-button-icons-only { width: 3.7em; } + +/*button text element */ +.ui-button .ui-button-text { display: block; line-height: 1.4; } +.ui-button-text-only .ui-button-text { padding: .4em 1em; } +.ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; } +.ui-button-text-icon-primary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.1em; } +.ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 2.1em .4em 1em; } +.ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; } +/* no icon support for input elements, provide padding by default */ +input.ui-button { padding: .4em 1em; } + +/*button icon element(s) */ +.ui-button-icon-only .ui-icon, .ui-button-text-icon-primary .ui-icon, .ui-button-text-icon-secondary .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; } +.ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; } +.ui-button-text-icon-primary .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .5em; } +.ui-button-text-icon-secondary .ui-button-icon-secondary, .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; } +.ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; } + +/*button sets*/ +.ui-buttonset { margin-right: 7px; } +.ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; } + +/* workarounds */ +button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */ +/* + * jQuery UI Dialog 1.8.18 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Dialog#theming + */ +.ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; } +.ui-dialog .ui-dialog-titlebar { padding: .4em 1em; position: relative; } +.ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .1em 0; } +.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; } +.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; } +.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; } +.ui-dialog .ui-dialog-content { position: relative; border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; } +.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; } +.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: right; } +.ui-dialog .ui-dialog-buttonpane button { margin: .5em .4em .5em 0; cursor: pointer; } +.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; } +.ui-draggable .ui-dialog-titlebar { cursor: move; } +/* + * jQuery UI Slider 1.8.18 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Slider#theming + */ +.ui-slider { position: relative; text-align: left; } +.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; } +.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; } + +.ui-slider-horizontal { height: .8em; } +.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; } +.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; } +.ui-slider-horizontal .ui-slider-range-min { left: 0; } +.ui-slider-horizontal .ui-slider-range-max { right: 0; } + +.ui-slider-vertical { width: .8em; height: 100px; } +.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; } +.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; } +.ui-slider-vertical .ui-slider-range-min { bottom: 0; } +.ui-slider-vertical .ui-slider-range-max { top: 0; }/* + * jQuery UI Tabs 1.8.18 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Tabs#theming + */ +.ui-tabs { position: relative; padding: .2em; zoom: 1; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */ +.ui-tabs .ui-tabs-nav { margin: 0; padding: .2em .2em 0; } +.ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 1px; margin: 0 .2em 1px 0; border-bottom: 0 !important; padding: 0; white-space: nowrap; } +.ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; } +.ui-tabs .ui-tabs-nav li.ui-tabs-selected { margin-bottom: 0; padding-bottom: 1px; } +.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; } +.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */ +.ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: none; } +.ui-tabs .ui-tabs-hide { display: none !important; } +/* + * jQuery UI Datepicker 1.8.18 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Datepicker#theming + */ +.ui-datepicker { width: 17em; padding: .2em .2em 0; display: none; } +.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; } +.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; } +.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; } +.ui-datepicker .ui-datepicker-prev { left:2px; } +.ui-datepicker .ui-datepicker-next { right:2px; } +.ui-datepicker .ui-datepicker-prev-hover { left:1px; } +.ui-datepicker .ui-datepicker-next-hover { right:1px; } +.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; } +.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; } +.ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; } +.ui-datepicker select.ui-datepicker-month-year {width: 100%;} +.ui-datepicker select.ui-datepicker-month, +.ui-datepicker select.ui-datepicker-year { width: 49%;} +.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; } +.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; } +.ui-datepicker td { border: 0; padding: 1px; } +.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; } +.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; } +.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; } +.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; } + +/* with multiple calendars */ +.ui-datepicker.ui-datepicker-multi { width:auto; } +.ui-datepicker-multi .ui-datepicker-group { float:left; } +.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; } +.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; } +.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; } +.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; } +.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; } +.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; } +.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; } +.ui-datepicker-row-break { clear:both; width:100%; font-size:0em; } + +/* RTL support */ +.ui-datepicker-rtl { direction: rtl; } +.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; } +.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; } +.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; } +.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; } +.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; } +.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; } +.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; } +.ui-datepicker-rtl .ui-datepicker-group { float:right; } +.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; } +.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; } + +/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */ +.ui-datepicker-cover { + display: none; /*sorry for IE5*/ + display/**/: block; /*sorry for IE5*/ + position: absolute; /*must have*/ + z-index: -1; /*must have*/ + filter: mask(); /*must have*/ + top: -4px; /*must have*/ + left: -4px; /*must have*/ + width: 200px; /*must have*/ + height: 200px; /*must have*/ +}/* + * jQuery UI Progressbar 1.8.18 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Progressbar#theming + */ +.ui-progressbar { height:2em; text-align: left; overflow: hidden; } +.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; } \ No newline at end of file diff --git a/Plugin-AquariumStatus/status.js b/Plugin-AquariumStatus/status.js index 36ae494..2ced93c 100644 --- a/Plugin-AquariumStatus/status.js +++ b/Plugin-AquariumStatus/status.js @@ -1 +1 @@ -var aq={"time":"now","Temperature":"24.56","pH":"3.73"}; \ No newline at end of file +var aq={"time":"now","Temperature":"0","pH":"0","Microsiemens":"0","TDS":"0","Salinity":"0","CO2":"0","AirTemperature":"0","Humidity":"0"}; \ No newline at end of file diff --git a/Plugin-AquariumStatus/ui.css b/Plugin-AquariumStatus/ui.css new file mode 100644 index 0000000..6fcb5c4 --- /dev/null +++ b/Plugin-AquariumStatus/ui.css @@ -0,0 +1,9 @@ +body{font-family:Verdana,Helvetica,sans-serif;} +#content{width:800px;margin:0 auto;} +.input{font-size:14px;} +.plug label{float:left;margin-right:10px;} +.slide{float:left;width:85%} +#status label {display: block; width: 100px; text-align: right; float: left; padding-right: 10px;} +#status span {display: block; float: left; } +.interval input {width: 20px; vertical-align:middle;padding-left:3px;} +.interval label {font-size: 14px; line-height:20px; vertical-align: middle;} \ No newline at end of file diff --git a/Plugin-CO2/CO2.cs b/Plugin-CO2/CO2.cs new file mode 100644 index 0000000..0d4f26e --- /dev/null +++ b/Plugin-CO2/CO2.cs @@ -0,0 +1,146 @@ +using Controller; +using System; +using Microsoft.SPOT; +using Microsoft.SPOT.Hardware; +using System.IO.Ports; +using System.Text; +using System.Threading; +using System.Collections; + +namespace Plugins +{ + public class COIIData : IPluginData + { + private PluginData[] _PluginData; + public PluginData[] GetData() { return _PluginData; } + public void SetData(PluginData[] _value) { _PluginData = _value; } + } + + /// + /// CO2 Sensor Available at: + /// http://sandboxelectronics.com/store/index.php?main_page=product_info&cPath=66&products_id=197 + /// This sensor is connected to an arduono. See the ArduinoBridge code to see how it handles averything. + /// + public class CO2 : InputPlugin + { + private TimeSpan m_timerInterval; + public override bool ImplimentsEventHandler() { return false; } + public CO2() { } + public CO2(object _config) : base() + { + Hashtable config = (Hashtable)_config; + // The Timer Interval is specified in an Arraylist of numbers + ArrayList interval = config["interval"] as ArrayList; + + //TODO: Double casting since for some reason an explicit cast from a Double + // to an Int doesn't work. It's a boxing issue, as interval[i] returns an object. + // And JSON.cs returns numbers as doubles + int[] times = new int[3]; + for (int i = 0; i < 3; i++) { times[i] = (int)(double)interval[i]; } + m_timerInterval = new TimeSpan(times[0], times[1], times[2]); + } + + ~CO2() { Dispose(); } + public override void Dispose() { } + + public override TimeSpan TimerInterval { get { return m_timerInterval; } } + public IPluginData GetData() { return null; } + + // Temperature doesn't need the Output Handler + public override void EventHandler(object sender, IPluginData data) + { + throw new System.NotImplementedException(); + } + + public override void TimerCallback(object state) + { + COIIData _CO2Data = new COIIData(); + // get current temperature + _CO2Data.SetData(ReadSensor()); + + foreach (PluginData pd in _CO2Data.GetData()) + { + Debug.Print(pd.Name + " = " + pd.Value.ToString("F")); + } + //Timer Callbacks receive a Delegate in the state object + InputDataAvailable ida = (InputDataAvailable)state; + + // call out to the delegate with expected value + ida(_CO2Data); + } + + /// + /// Obtain CO2 value + /// + /// PluginData Array + /// For use with the MG-811 CO2 Sensor and arduino bridge + private PluginData[] ReadSensor() + { + double CO2 = 0.0; + bool ReadSuccess = true; + PluginData[] _PluginData = new PluginData[1]; + _PluginData[0] = new PluginData(); + SerialPort sp = new SerialPort(SecretLabs.NETMF.Hardware.Netduino.SerialPorts.COM4, 57600, Parity.None, 8, StopBits.One); + sp.ReadTimeout = 3000; + sp.WriteTimeout = 3000; + + try + { + string command = ""; + string response = ""; + char inChar; + + command = "CO2\n"; + + byte[] message = Encoding.UTF8.GetBytes(command); + + sp.Open(); + sp.Write(message, 0, message.Length); + sp.Flush(); + Thread.Sleep(1000); + + // Now collect response + try + { + while (sp.BytesToRead > 0) + { + inChar = (char)sp.ReadByte(); + if (inChar != '\r' && inChar != '\0') + response += inChar; + } + Debug.Print("CO2 Stamp Response:" + response); + } + catch (Exception e) + { + Debug.Print("Could not read from CO2 Stamp. Please check connection."); + ReadSuccess = false; + } + + if (response.Length > 0) + { + CO2 = double.Parse(response); + } + else ReadSuccess = false; + + } + catch (Exception e) + { + Debug.Print(e.Message); + ReadSuccess = false; + } + finally + { + _PluginData[0].Name = "CO2"; + _PluginData[0].UnitOFMeasurment = "PPM"; + _PluginData[0].Value = CO2; + _PluginData[0].ThingSpeakFieldID = 3; + _PluginData[0].LastReadSuccess = ReadSuccess; + + if(sp.IsOpen) + sp.Close(); + sp.Dispose(); + } + return _PluginData; + } + } +} \ No newline at end of file diff --git a/Plugin-CO2/CO2.htm b/Plugin-CO2/CO2.htm new file mode 100644 index 0000000..a25faa8 --- /dev/null +++ b/Plugin-CO2/CO2.htm @@ -0,0 +1 @@ +
CO2

CO2 Meter

\ No newline at end of file diff --git a/Plugin-CO2/CO2.js b/Plugin-CO2/CO2.js new file mode 100644 index 0000000..0263999 --- /dev/null +++ b/Plugin-CO2/CO2.js @@ -0,0 +1,13 @@ +var CO2Init = function () { + var pluginEnabled = (config.config.input.CO2.enabled === 'true'); + var co2Enable = $('#CO2'); + co2Enable.attr('checked', pluginEnabled); + co2Enable.click(function () { + $(this).button("option", "label", this.checked ? "Enabled" : "Disabled"); + config.config.input.CO2.enabled = this.checked ? 'true' : 'false'; + }); + co2Enable.button({ label: (pluginEnabled ? "Enabled" : "Disabled") }).button('refresh'); + $("#co2H").val(config.config.input.CO2.interval[0]); + $("#co2M").val(config.config.input.CO2.interval[1]); + $("#co2S").val(config.config.input.CO2.interval[2]); +}; \ No newline at end of file diff --git a/Plugin-CO2/Plugin-CO2.csproj b/Plugin-CO2/Plugin-CO2.csproj new file mode 100644 index 0000000..4c65814 --- /dev/null +++ b/Plugin-CO2/Plugin-CO2.csproj @@ -0,0 +1,73 @@ + + + + CO2 + Library + Plugins + {b69e3092-b931-443c-abe7-7e7b65f2a37f};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 9.0.21022 + 2.0 + {A71793DB-2334-4922-9139-4CCEF2B3EE31} + v4.2 + $(MSBuildExtensionsPath32)\Microsoft\.NET Micro Framework\ + + + true + full + false + bin\Debug\ + TRACE;DEBUG;TRACE,MF_FRAMEWORK_VERSION_V4_2 + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + bin\Emulator\ + DEBUG;TRACE,MF_FRAMEWORK_VERSION_V4_2 + true + full + AnyCPU + false + prompt + true + true + + + OnOutputUpdated + + + + + + + + + {117848BD-213C-4AE4-8F58-D27F14DAA534} + Controller + + + + + + + + + + + + + + copy $(TargetDir)le\$(TargetName).pe g:\plugins + +copy $(ProjectDir)$(TargetName).htm g:\plugins +copy $(ProjectDir)$(TargetName).js g:\files\$(TargetName).min.js + + \ No newline at end of file diff --git a/Plugin-CO2/Properties/AssemblyInfo.cs b/Plugin-CO2/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..95a868a --- /dev/null +++ b/Plugin-CO2/Properties/AssemblyInfo.cs @@ -0,0 +1,26 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Resources; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Input Plugin - Temperature")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("fishfornerds.com")] +[assembly: AssemblyProduct("Controller - Input Plugin")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/Plugin-DHTSensor/DHTSensor.htm b/Plugin-DHTSensor/DHTSensor.htm new file mode 100644 index 0000000..b558fe9 --- /dev/null +++ b/Plugin-DHTSensor/DHTSensor.htm @@ -0,0 +1,2 @@ +
DHT Sensor

+ DHT11 | DHT22 Temp & Humidity Sensor

\ No newline at end of file diff --git a/Plugin-DHTSensor/DHTSensor.js b/Plugin-DHTSensor/DHTSensor.js new file mode 100644 index 0000000..b7dc151 --- /dev/null +++ b/Plugin-DHTSensor/DHTSensor.js @@ -0,0 +1,13 @@ +var DHTSensorInit = function () { + var pluginEnabled = (config.config.input.DHTSensor.enabled === 'true'); + var DHTSensorEnable = $('#PDHTSensorE'); + DHTSensorEnable.attr('checked', pluginEnabled); + DHTSensorEnable.click(function () { + $(this).button("option", "label", this.checked ? "Enabled" : "Disabled"); + config.config.input.DHTSensor.enabled = this.checked ? 'true' : 'false'; + }); +DHTSensorEnable.button({ label: (pluginEnabled ? "Enabled" : "Disabled") }).button('refresh'); +$("#DHTSensoriH").val(config.config.input.DHTSensor.interval[0]); +$("#DHTSensoriM").val(config.config.input.DHTSensor.interval[1]); +$("#DHTSensoriS").val(config.config.input.DHTSensor.interval[2]); +}; \ No newline at end of file diff --git a/Plugin-DHTSensor/DhtSensor.cs b/Plugin-DHTSensor/DhtSensor.cs new file mode 100644 index 0000000..3aae49e --- /dev/null +++ b/Plugin-DHTSensor/DhtSensor.cs @@ -0,0 +1,164 @@ +using Controller; +using System; +using Microsoft.SPOT; +using Microsoft.SPOT.Hardware; +using System.IO.Ports; +using System.Text; +using System.Threading; +using System.Collections; + + +namespace Plugins +{ + public class HT : IPluginData + { + private PluginData[] _PluginData; + public PluginData[] GetData() { return _PluginData; } + public void SetData(PluginData[] _value) { _PluginData = _value; } + } + + /// + /// Plugin for DHT11 Temperature and Humidity sensor. + /// This sensor is connected to the arduino bridge because I couldn't + /// get the bit banger code for this device to work properly. + /// + public class DHTSensor : InputPlugin + { + public override bool ImplimentsEventHandler() { return false; } + private TimeSpan m_timerInterval; + public IPluginData GetData() { return null; } + + public DHTSensor() { } + public DHTSensor(object _config) : base() + { + Hashtable config = (Hashtable)_config; + // The Timer Interval is specified in an Arraylist of numbers + ArrayList interval = config["interval"] as ArrayList; + + //TODO: Double casting since for some reason an explicit cast from a Double + // to an Int doesn't work. It's a boxing issue, as interval[i] returns an object. + // And JSON.cs returns numbers as doubles + int[] times = new int[3]; + for (int i = 0; i < 3; i++) { times[i] = (int)(double)interval[i]; } + m_timerInterval = new TimeSpan(times[0], times[1], times[2]); + } + + ~DHTSensor() { Dispose(); } + public override void Dispose() { } + + public override TimeSpan TimerInterval { get { return m_timerInterval; } } + + // Temperature doesn't need the Output Handler + public override void EventHandler(object sender, IPluginData data) + { + throw new System.NotImplementedException(); + } + + public override void TimerCallback(object state) + { + Debug.Print("DHTSensor Callback"); + HT _DHTData = new HT(); + // get current readings + _DHTData.SetData(ReadSensor()); + + foreach (PluginData pd in _DHTData.GetData()) + { + Debug.Print(pd.Name + " = " + pd.Value.ToString("F")); + } + //Timer Callbacks receive a Delegate in the state object + InputDataAvailable ida = (InputDataAvailable)state; + + // call out to the delegate with expected value + ida(_DHTData); + } + + /// + /// Read DHT Data + /// + /// Float value of current Temperature reading + private PluginData[] ReadSensor() + { + PluginData[] _PluginData = new PluginData[2]; + _PluginData[0] = new PluginData(); + _PluginData[1] = new PluginData(); + Double Temp = 0; + Double Humidity = 0; + bool ReadSuccess = true; + + SerialPort sp = new SerialPort(SecretLabs.NETMF.Hardware.Netduino.SerialPorts.COM4, 57600, Parity.None, 8, StopBits.One); + sp.ReadTimeout = 3000; + sp.WriteTimeout = 3000; + + try + { + string command = ""; + string response = ""; + char inChar; + + command = "R\n"; + + byte[] message = Encoding.UTF8.GetBytes(command); + + sp.Open(); + sp.Write(message, 0, message.Length); + sp.Flush(); + //Debug.Print("Sending \"" + command + "\" to the DHT Bridge"); + Thread.Sleep(1000); + + // Now collect response + try + { + while (sp.BytesToRead > 0) + { + inChar = (char)sp.ReadByte(); + if (inChar != '\r' && inChar != '\0') + response += inChar; + } + Debug.Print("DHT Response:" + response); + } + catch(Exception e) + { + Debug.Print("Could not read from DHT Stamp. Please check connection."); + ReadSuccess = false; + } + + if (response.Length > 0) + { + string[] _split; + _split = response.Split(','); + Temp = double.Parse(_split[0]); + Humidity = double.Parse(_split[1]); + } + else ReadSuccess = false; + + } + catch (Exception e) + { + Debug.Print(e.Message); + ReadSuccess = false; + } + finally + { + //Assign the response to a PluginData Variable + + for (int i=0;i< _PluginData.Length;i++) + _PluginData[i].LastReadSuccess = ReadSuccess; + + _PluginData[0].Value = Temp; + _PluginData[0].Name = "AirTemperature"; + _PluginData[0].UnitOFMeasurment = "C"; + _PluginData[0].ThingSpeakFieldID = 7; + + _PluginData[1].Value = Humidity; + _PluginData[1].Name = "Humidity"; + _PluginData[1].UnitOFMeasurment = "%"; + _PluginData[1].ThingSpeakFieldID = 8; + + if (sp.IsOpen) + sp.Close(); + sp.Dispose(); + } + return _PluginData; + } + } +} \ No newline at end of file diff --git a/Plugin-DHTSensor/Plugin-DHTSensor.csproj b/Plugin-DHTSensor/Plugin-DHTSensor.csproj new file mode 100644 index 0000000..5aaf060 --- /dev/null +++ b/Plugin-DHTSensor/Plugin-DHTSensor.csproj @@ -0,0 +1,74 @@ + + + + DHTSensor + Library + Plugins + {b69e3092-b931-443c-abe7-7e7b65f2a37f};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 9.0.21022 + 2.0 + {D02941EE-C27E-4422-AD5E-FE99C4B822CA} + v4.2 + $(MSBuildExtensionsPath32)\Microsoft\.NET Micro Framework\ + + + true + full + false + bin\Debug\ + TRACE;DEBUG;TRACE,MF_FRAMEWORK_VERSION_V4_2 + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + bin\Emulator\ + DEBUG;TRACE,MF_FRAMEWORK_VERSION_V4_2 + true + full + AnyCPU + false + prompt + true + true + + + OnOutputUpdated + + + + + + + + + {117848BD-213C-4AE4-8F58-D27F14DAA534} + Controller + + + + + + + + + + + + + + + copy $(TargetDir)le\$(TargetName).pe g:\plugins +\ +copy $(ProjectDir)$(TargetName).htm g:\plugins\ +copy $(ProjectDir)$(TargetName).js g:\files\$(TargetName).min.js + + \ No newline at end of file diff --git a/Plugin-DHTSensor/Plugin-DHTSensor.sln b/Plugin-DHTSensor/Plugin-DHTSensor.sln new file mode 100644 index 0000000..50d8e61 --- /dev/null +++ b/Plugin-DHTSensor/Plugin-DHTSensor.sln @@ -0,0 +1,23 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin-DHTSensor", "Plugin-DHTSensor.csproj", "{79EFAD9E-C3E1-4D3F-993E-7CFF382A1C29}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Emulator|Any CPU = Emulator|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {79EFAD9E-C3E1-4D3F-993E-7CFF382A1C29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {79EFAD9E-C3E1-4D3F-993E-7CFF382A1C29}.Debug|Any CPU.Build.0 = Debug|Any CPU + {79EFAD9E-C3E1-4D3F-993E-7CFF382A1C29}.Emulator|Any CPU.ActiveCfg = Emulator|Any CPU + {79EFAD9E-C3E1-4D3F-993E-7CFF382A1C29}.Emulator|Any CPU.Build.0 = Emulator|Any CPU + {79EFAD9E-C3E1-4D3F-993E-7CFF382A1C29}.Release|Any CPU.ActiveCfg = Release|Any CPU + {79EFAD9E-C3E1-4D3F-993E-7CFF382A1C29}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Plugin-DHTSensor/Properties/AssemblyInfo.cs b/Plugin-DHTSensor/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..95a868a --- /dev/null +++ b/Plugin-DHTSensor/Properties/AssemblyInfo.cs @@ -0,0 +1,26 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Resources; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Input Plugin - Temperature")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("fishfornerds.com")] +[assembly: AssemblyProduct("Controller - Input Plugin")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/Plugin-ElectricalConductivity/ElectricalConductivity.cs b/Plugin-ElectricalConductivity/ElectricalConductivity.cs index ba4ccf3..d652f4b 100644 --- a/Plugin-ElectricalConductivity/ElectricalConductivity.cs +++ b/Plugin-ElectricalConductivity/ElectricalConductivity.cs @@ -1,6 +1,7 @@ using Controller; using System; using Microsoft.SPOT; +using Microsoft.SPOT.Hardware; using System.IO.Ports; using System.Text; using System.Threading; @@ -9,13 +10,11 @@ namespace Plugins { - public class ElectricalConductivityData : IPluginData + public class ECD : IPluginData { - private float m_value; - public float GetValue() { return m_value; } - public void SetValue(float _value) { m_value = _value; } - public string DataUnits() { return "ppm"; } - public ThingSpeakFields DataType() { return ThingSpeakFields.PPM; } + private PluginData[] _PluginData; + public PluginData[] GetData() { return _PluginData; } + public void SetData(PluginData[] _value) { _PluginData = _value; } } /// @@ -25,19 +24,27 @@ public class ElectricalConductivityData : IPluginData /// It also provides temperature compensation, so the EC plugin has both an /// Input Callback and and Output Callback, to allow the temperature input /// plugin to update the EC plugin. + /// + /// If you are using this in conjunction with Atlas' + /// PH meter, you need to put an N-Channel Mosfet in line with the probe's ground cable + /// to prevent interfering with the PH meter. + /// and switch on when taking a reading.. then back off when done. + /// /// public class ElectricalConductivity : InputPlugin { private TimeSpan m_timerInterval; + private int m_probetype; + public override bool ImplimentsEventHandler() { return true; } public override TimeSpan TimerInterval { get { return m_timerInterval; } } /// /// Latest temperature reading passed in from the Temperature Plugin /// - private float m_Temperature; + private PluginData m_Temperature = new PluginData(); - public ElectricalConductivity() { m_Temperature = 0.0F; } + public ElectricalConductivity() { } public ElectricalConductivity(object _config) : base() { Hashtable config = (Hashtable)_config; @@ -50,6 +57,10 @@ public ElectricalConductivity(object _config) : base() int[] times = new int[3]; for (int i = 0; i < 3; i++) { times[i] = (int)(double)interval[i]; } m_timerInterval = new TimeSpan(times[0], times[1], times[2]); + + //Set Probe Type + //m_probetype = int.Parse(config["ProbeType"].ToString()); + //SetProbeType(); } ~ElectricalConductivity() { Dispose(); } @@ -62,20 +73,29 @@ public override void Dispose() { } /// Last reading public override void EventHandler(object _sender, IPluginData _data) { - // Only worry about Temperature data, so check data units. - // If it's 'C' then assume it's the one we want. - Debug.Print("Got Temperature Value"); - if (_data.DataUnits().Equals("C")) m_Temperature = _data.GetValue(); + // Only worry about Temperature data, so check data units and name. + // If it's 'C' and 'Temperature' then assume it's the one we want. + foreach (PluginData _pd in _data.GetData()) + { + if (_pd.Name == "Temperature" && _pd.UnitOFMeasurment == "C" && _pd.LastReadSuccess) + { + Debug.Print("EC Stamp Got Temperature Value"); + m_Temperature = _pd; + } + } } public override void TimerCallback(object state) { Debug.Print("ElectricalConductivity Callback"); - ElectricalConductivityData ECData = new ElectricalConductivityData(); + ECD ECData = new ECD(); // get current ElectricalConductivity Value - ECData.SetValue(GetPPM()); - Debug.Print("ElectricalConductivity = " + ECData.GetValue().ToString("F")); + ECData.SetData(GetDataFromSensor()); + foreach (PluginData pd in ECData.GetData()) + { + Debug.Print(pd.Name + " = " + pd.Value.ToString()); + } //Timer Callbacks receive a Delegate in the state object InputDataAvailable ida = (InputDataAvailable)state; @@ -84,59 +104,167 @@ public override void TimerCallback(object state) // TODO: Currently there is a glitch with SerialPort and // sometimes data doesn't come back from the Stamp. // Discard bad readings, and report any meaningful ones - if (ECData.GetValue() > 0.0F) ida(ECData); + //if (((float)ECData.GetData()[0].Value) > 0.0F) + ida(ECData); } /// /// Takes reading from Atlas Scientific ElectricalConductivity Stamp /// /// - private float GetPPM() + private PluginData[] GetDataFromSensor() { - float PPM = 0.0F; - SerialPort sp = new SerialPort(Serial.COM2, 38400, Parity.None, 8, StopBits.One); - sp.ReadTimeout = 1000; + PluginData[] _Data = new PluginData[3]; + _Data[0] = new PluginData(); + _Data[1] = new PluginData(); + _Data[2] = new PluginData(); + + SerialPort sp = new SerialPort(Serial.COM1, 38400, Parity.None, 8, StopBits.One); + sp.ReadTimeout = 4000; + sp.WriteTimeout = 4000; + double Microsiemens = 0; + double TDS = 0; + double Salinity = 0; + bool MSReadSuccess = true; + bool TDSReadSuccess = true; + bool SReadSuccess = true; try { string command = ""; string response = ""; char inChar; - string ppm = ""; - + // Send the temperature reading if available - if (m_Temperature > 0) - command = m_Temperature.ToString("F") + "\rR\r"; + if (m_Temperature.LastReadSuccess) + command = '\r' + m_Temperature.Value.ToString("F") + "\r"; else - command = "R\r"; + command = "\rR\r"; - Debug.Print(command); byte[] message = Encoding.UTF8.GetBytes(command); sp.Open(); sp.Write(message, 0, message.Length); sp.Flush(); - Debug.Print("sending message"); + Debug.Print("Sending \"" + command + "\" to the EC stamp"); + Thread.Sleep(3000); // Now collect response - while ((inChar = (char)sp.ReadByte()) != '\r') { response += inChar; } + try + { + while (sp.BytesToRead > 0) + { + inChar = (char)sp.ReadByte(); + if (inChar != '\r' && inChar != '\0') + response += inChar; + } + Debug.Print("Response:" + response); + } + catch(Exception e) + { + Debug.Print("Could not read from EC Stamp. Please check connection."); + MSReadSuccess = false; + TDSReadSuccess = false; + SReadSuccess = false; + } - response = response.Split(',')[1]; - - // Stamp can return text if reading was not successful, so test before returning - double ppmReading; - if (Double.TryParse(response, out ppmReading)) PPM = (float)ppmReading; - } - catch (Exception e) - { - Debug.Print(e.StackTrace); - } - finally - { - sp.Close(); + if (response.Length > 0) + { + string[] _split; + _split = response.Split(','); + try { Microsiemens = double.Parse(_split[0]); } + catch (Exception e) { MSReadSuccess = false; } + try { TDS = double.Parse(_split[1]); } + catch (Exception e) { TDSReadSuccess = false; } + try { Salinity = double.Parse(_split[2]); } + catch (Exception e) { SReadSuccess = false; } + } + else + { + MSReadSuccess = false; + TDSReadSuccess = false; + SReadSuccess = false; + } + + } + catch (Exception e) + { + Debug.Print(e.Message); + MSReadSuccess = false; + TDSReadSuccess = false; + SReadSuccess = false; + } + finally + { + //Assign the response to a PluginData Variable + _Data[0].Name = "Microsiemens"; + _Data[0].UnitOFMeasurment = "µs"; + _Data[0].Value = Microsiemens; + _Data[0].ThingSpeakFieldID = 4; + _Data[0].LastReadSuccess = MSReadSuccess; + + _Data[1].Name = "TDS"; + _Data[1].UnitOFMeasurment = "PPM"; + _Data[1].Value = TDS; + _Data[1].ThingSpeakFieldID = 5; + _Data[1].LastReadSuccess = TDSReadSuccess; + + _Data[2].Name = "Salinity"; + _Data[2].UnitOFMeasurment = "Salinity"; + _Data[2].Value = Salinity; + _Data[2].ThingSpeakFieldID = 6; + _Data[2].LastReadSuccess = SReadSuccess; + + if(sp.IsOpen) sp.Close(); sp.Dispose(); } - return PPM; + return _Data; } - } + + //private void SetProbeType() + //{ + // SerialPort sp = new SerialPort(Serial.COM1, 38400, Parity.None, 8, StopBits.One); + // sp.ReadTimeout = 10000; + // try + // { + // string command = "\rP," + m_probetype.ToString() + "\r"; + // string response = ""; + // char inChar; + + // byte[] message = Encoding.UTF8.GetBytes(command); + + // sp.Open(); + // sp.Write(message, 0, message.Length); + // sp.Flush(); + // Debug.Print("Sending \"" + command + "\" to the EC stamp"); + // Thread.Sleep(2000); + + // // Now collect response + // try + // { + // while (sp.BytesToRead > 0) + // { + // inChar = (char)sp.ReadByte(); + // if (inChar != '\r' && inChar != '\0') + // response += inChar; + // } + // Debug.Print("Response:" + response); + // } + // catch (Exception e) + // { + // Debug.Print("Could not set EC Probe. Please check connection."); + // } + + // } + // catch (Exception e) + // { + // Debug.Print(e.Message); + // } + // finally + // { + // if (sp.IsOpen) sp.Close(); + // sp.Dispose(); + // } + //} + } } \ No newline at end of file diff --git a/Plugin-ElectricalConductivity/ElectricalConductivity.htm b/Plugin-ElectricalConductivity/ElectricalConductivity.htm index 899efa8..2f7764b 100644 --- a/Plugin-ElectricalConductivity/ElectricalConductivity.htm +++ b/Plugin-ElectricalConductivity/ElectricalConductivity.htm @@ -1 +1,12 @@ -
ElectricalConductivity

ElectricalConductivity Meter

\ No newline at end of file +
+ ElectricalConductivity + + +

ElectricalConductivity Meter

+ + +
\ No newline at end of file diff --git a/Plugin-ElectricalConductivity/ElectricalConductivity.js b/Plugin-ElectricalConductivity/ElectricalConductivity.js index 3468b10..9515a10 100644 --- a/Plugin-ElectricalConductivity/ElectricalConductivity.js +++ b/Plugin-ElectricalConductivity/ElectricalConductivity.js @@ -1,13 +1,19 @@ var ElectricalConductivityInit = function () { var pluginEnabled = (config.config.input.ElectricalConductivity.enabled === 'true'); var ElectricalConductivityEnable = $('#PElectricalConductivityE'); - ElectricalConductivityEnable.attr('checked', pluginEnabled); - ElectricalConductivityEnable.click(function () { - $(this).button("option", "label", this.checked ? "Enabled" : "Disabled"); - config.config.input.ElectricalConductivity.enabled = this.checked ? 'true' : 'false'; - }); - ElectricalConductivityEnable.button({ label: (pluginEnabled ? "Enabled" : "Disabled") }).button('refresh'); - $("#ElectricalConductivityH").val(config.config.input.ElectricalConductivity.interval[0]); - $("#ElectricalConductivityM").val(config.config.input.ElectricalConductivity.interval[1]); - $("#ElectricalConductivityS").val(config.config.input.ElectricalConductivity.interval[2]); + ElectricalConductivityEnable.attr('checked', pluginEnabled); + ElectricalConductivityEnable.click(function () { + $(this).button("option", "label", this.checked ? "Enabled" : "Disabled"); + config.config.input.ElectricalConductivity.enabled = this.checked ? 'true' : 'false'; + }); + var ProbeType = config.config.input.ElectricalConductivity.ProbeType; + $("#ProbeType").val(ProbeType); + $('select[name="ProbeType"]').change(function () { + ProbeType = $(this).val(); + config.config.input.ElectricalConductivity.ProbeType = ProbeType + }); + ElectricalConductivityEnable.button({ label: (pluginEnabled ? "Enabled" : "Disabled") }).button('refresh'); + $("#ElectricalConductivityH").val(config.config.input.ElectricalConductivity.interval[0]); + $("#ElectricalConductivityM").val(config.config.input.ElectricalConductivity.interval[1]); + $("#ElectricalConductivityS").val(config.config.input.ElectricalConductivity.interval[2]); }; \ No newline at end of file diff --git a/Plugin-ElectricalConductivity/ElectricalConductivity.min.js b/Plugin-ElectricalConductivity/ElectricalConductivity.min.js deleted file mode 100644 index 9edc09e..0000000 --- a/Plugin-ElectricalConductivity/ElectricalConductivity.min.js +++ /dev/null @@ -1 +0,0 @@ -var ElectricalConductivityInit=function(){var pluginEnabled=(config.config.input.ElectricalConductivity.enabled==='true');var ElectricalConductivityEnable=$('#PElectricalConductivityE');ElectricalConductivityEnable.attr('checked',pluginEnabled);ElectricalConductivityEnable.click(function(){$(this).button("option","label",this.checked?"Enabled":"Disabled");config.config.input.ElectricalConductivity.enabled=this.checked?'true':'false';});ElectricalConductivityEnable.button({label:(pluginEnabled?"Enabled":"Disabled")}).button('refresh');}; \ No newline at end of file diff --git a/Plugin-ElectricalConductivity/Plugin-ElectricalConductivity.csproj b/Plugin-ElectricalConductivity/Plugin-ElectricalConductivity.csproj index 43f4352..6dd60ce 100644 --- a/Plugin-ElectricalConductivity/Plugin-ElectricalConductivity.csproj +++ b/Plugin-ElectricalConductivity/Plugin-ElectricalConductivity.csproj @@ -1,7 +1,7 @@  - pH + ElectricalConductivity Library Plugins {b69e3092-b931-443c-abe7-7e7b65f2a37f};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} @@ -33,8 +33,8 @@ - + @@ -55,7 +55,10 @@ - copy $(TargetDir)le\$(TargetName).pe g:\plugins -copy $(ProjectDir)$(TargetName).htm g:\plugins + copy $(TargetDir)le\$(TargetName).pe g:\plugins\ + + +copy $(ProjectDir)$(TargetName).htm g:\plugins\ +copy $(ProjectDir)$(TargetName).js g:\files\$(TargetName).min.js \ No newline at end of file diff --git a/Plugin-ElectricalConductivity/Plugin-ElectricalConductivity.sln b/Plugin-ElectricalConductivity/Plugin-ElectricalConductivity.sln deleted file mode 100644 index 187398b..0000000 --- a/Plugin-ElectricalConductivity/Plugin-ElectricalConductivity.sln +++ /dev/null @@ -1,20 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2010 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin-ElectricalConductivity", "Plugin-ElectricalConductivity.csproj", "{428E9964-527F-48D8-B88A-8A22435F481B}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {428E9964-527F-48D8-B88A-8A22435F481B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {428E9964-527F-48D8-B88A-8A22435F481B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {428E9964-527F-48D8-B88A-8A22435F481B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {428E9964-527F-48D8-B88A-8A22435F481B}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/Plugin-Logfile/Logfile.cs b/Plugin-Logfile/Logfile.cs index 24e8e88..299d295 100644 --- a/Plugin-Logfile/Logfile.cs +++ b/Plugin-Logfile/Logfile.cs @@ -1,46 +1,61 @@ -using System; -using System.Collections; -using System.IO; -using Controller; -using Microsoft.SPOT; -using System.Text; - -namespace Plugins -{ - public class Logfile : OutputPlugin - { - ~Logfile() { Dispose(); } - public override void Dispose() { } - - private string m_logFile; - - public Logfile(object _config) - { - Hashtable config = (Hashtable)_config; - m_logFile = config["filename"].ToString(); - config = null; - } - - public override void EventHandler(Object sender, IPluginData data) - { - StringBuilder sb = new StringBuilder(); - - // Format string for output - sb.Append(DateTime.Now.ToString("s")); - sb.Append(","); - sb.Append(data.GetValue().ToString("F")); - sb.Append(data.DataUnits()); - sb.Append("\n"); - - // take data and write it out to text - using (FileStream fs = new FileStream(m_logFile, FileMode.Append)) - { - var buf = Encoding.UTF8.GetBytes(sb.ToString()); - fs.Write(buf, 0, buf.Length); - fs.Flush(); - } - - Debug.Print("Written " + sb.ToString()); - } - } -} +using System; +using System.Collections; +using System.IO; +using Controller; +using Microsoft.SPOT; +using System.Text; + +namespace Plugins +{ + public class Logfile : OutputPlugin + { + ~Logfile() { Dispose(); } + public override void Dispose() { } + + private string m_logFile; + + public Logfile(object _config) + { + Hashtable config = (Hashtable)_config; + m_logFile = config["filename"].ToString(); + config = null; + } + + /// + /// Appends received data to the log file. + /// + /// + /// + public override void EventHandler(Object sender, IPluginData data) + { + try + { + StringBuilder sb = new StringBuilder(); + foreach (PluginData _PluginData in data.GetData()) + { + sb.Append('"'); + sb.Append(DateTime.Now.ToString("s")); + sb.Append('"'); + sb.Append("," + '"'); + sb.Append(_PluginData.Name); + sb.Append('"' + "," + '"'); + sb.Append(_PluginData.Value); + sb.Append('"' + "," + '"'); + sb.Append(_PluginData.LastReadSuccess.ToString()); + sb.AppendLine("\""); + } + // take data and write it out to text + using (FileStream fs = new FileStream(m_logFile, FileMode.Append)) + { + var buf = Encoding.UTF8.GetBytes(sb.ToString()); + fs.Write(buf, 0, buf.Length); + fs.Flush(); + } + } + catch (Exception e) + { + Debug.Print(e.Message); + } + } + } +} diff --git a/Plugin-Logfile/Plugin-Logfile.csproj b/Plugin-Logfile/Plugin-Logfile.csproj index 98c01b7..ea517fe 100644 --- a/Plugin-Logfile/Plugin-Logfile.csproj +++ b/Plugin-Logfile/Plugin-Logfile.csproj @@ -1,67 +1,69 @@ - - - - Logfile - Library - Plugins - {b69e3092-b931-443c-abe7-7e7b65f2a37f};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 9.0.21022 - 2.0 - {65B2EAC1-B841-4134-9229-B24583B46FFB} - v4.2 - $(MSBuildExtensionsPath32)\Microsoft\.NET Micro Framework\ - - - true - full - false - bin\Debug\ - TRACE;DEBUG;TRACE,MF_FRAMEWORK_VERSION_V4_2 - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - true - bin\Emulator\ - DEBUG;TRACE,MF_FRAMEWORK_VERSION_V4_2 - true - full - AnyCPU - false - prompt - - - OnOutputUpdated - - - - - - - - - {117848BD-213C-4AE4-8F58-D27F14DAA534} - Controller - - - - - - - - - - - + + + + Logfile + Library + Plugins + {b69e3092-b931-443c-abe7-7e7b65f2a37f};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 9.0.21022 + 2.0 + {65B2EAC1-B841-4134-9229-B24583B46FFB} + v4.2 + $(MSBuildExtensionsPath32)\Microsoft\.NET Micro Framework\ + + + true + full + false + bin\Debug\ + TRACE;DEBUG;TRACE,MF_FRAMEWORK_VERSION_V4_2 + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + bin\Emulator\ + DEBUG;TRACE,MF_FRAMEWORK_VERSION_V4_2 + true + full + AnyCPU + false + prompt + + + OnOutputUpdated + + + + + + + + + {117848BD-213C-4AE4-8F58-D27F14DAA534} + Controller + + + + + + + + + + + copy $(TargetDir)le\$(TargetName).pe g:\plugins -copy $(ProjectDir)$(TargetName).htm g:\plugins - +\ +copy $(ProjectDir)$(TargetName).htm g:\plugins\ +copy $(ProjectDir)$(TargetName).js g:\files\$(TargetName).min.js + \ No newline at end of file diff --git a/Plugin-Logfile/logfile.js b/Plugin-Logfile/logfile.js index a6cbc7b..d65c40e 100644 --- a/Plugin-Logfile/logfile.js +++ b/Plugin-Logfile/logfile.js @@ -1,4 +1,4 @@ -var logfileInit = function() { +var LogfileInit = function() { var pluginEnabled = (config.config.output.Logfile.enabled === 'true'); $("#FN").val(config.config.output.Logfile.filename); $("#FN").change(function() { config.config.output.Logfile.filename = $(this).val(); }); diff --git a/Plugin-Relays/Plugin-Relays.csproj b/Plugin-Relays/Plugin-Relays.csproj index 752dadc..8f7af79 100644 --- a/Plugin-Relays/Plugin-Relays.csproj +++ b/Plugin-Relays/Plugin-Relays.csproj @@ -1,70 +1,72 @@ - - - - Relays - Library - Plugins - {b69e3092-b931-443c-abe7-7e7b65f2a37f};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 9.0.21022 - 2.0 - {E9EB4D78-EEC4-4A40-9DE4-CB1EEA20977B} - v4.2 - $(MSBuildExtensionsPath32)\Microsoft\.NET Micro Framework\ - - - true - full - false - bin\Debug\ - TRACE;DEBUG;TRACE,MF_FRAMEWORK_VERSION_V4_2 - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - true - bin\Emulator\ - DEBUG;TRACE,MF_FRAMEWORK_VERSION_V4_2 - true - full - AnyCPU - false - prompt - - - OnOutputUpdated - - - - - - - - - - - - - - - - {117848BD-213C-4AE4-8F58-D27F14DAA534} - Controller - - - - - - - + + + + Relays + Library + Plugins + {b69e3092-b931-443c-abe7-7e7b65f2a37f};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 9.0.21022 + 2.0 + {E9EB4D78-EEC4-4A40-9DE4-CB1EEA20977B} + v4.2 + $(MSBuildExtensionsPath32)\Microsoft\.NET Micro Framework\ + + + true + full + false + bin\Debug\ + TRACE;DEBUG;TRACE,MF_FRAMEWORK_VERSION_V4_2 + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + bin\Emulator\ + DEBUG;TRACE,MF_FRAMEWORK_VERSION_V4_2 + true + full + AnyCPU + false + prompt + + + OnOutputUpdated + + + + + + + + + + + + + + + + {117848BD-213C-4AE4-8F58-D27F14DAA534} + Controller + + + + + + + copy $(TargetDir)le\$(TargetName).pe g:\plugins -copy $(ProjectDir)$(TargetName).htm g:\plugins - +\ +copy $(ProjectDir)$(TargetName).htm g:\plugins\ +copy $(ProjectDir)$(TargetName).js g:\files\$(TargetName).min.js + \ No newline at end of file diff --git a/Plugin-Relays/Relays.cs b/Plugin-Relays/Relays.cs index ce0b598..50c61ac 100644 --- a/Plugin-Relays/Relays.cs +++ b/Plugin-Relays/Relays.cs @@ -1,112 +1,366 @@ -using System; -using System.Collections; -using Controller; -using Microsoft.SPOT; -using Microsoft.SPOT.Hardware; -using SecretLabs.NETMF.Hardware; -using SecretLabs.NETMF.Hardware.NetduinoPlus; - -namespace Plugins -{ - /// - /// Control Plugin to manage the RelayShield http://www.seeedstudio.com/depot/relay-shield-p-693.html?cPath=132_134 - /// Used to control lighting and ATO solenoid - /// - public class Relays : ControlPlugin - { - ~Relays() { Dispose(); } - public override void Dispose() { m_relayPins = null; m_commands = null; } - - private Hashtable m_commands; - public override Hashtable Commands() { return m_commands; } - - /// - /// Used during Command parsing to trigger immediate command execution. - /// Example: during mid-day, the lights should be on, but a reboot of the controller will restart - /// the Netduino, forcing a re-parse of the ini file. - /// The timers will be setup to turn on the next day, but they need to be on immediately as well - /// - private bool m_missedExecute = false; - - private OutputPort[] m_relayPins; - public Relays(object _config) - { - // Initialize Relay Pin controls - m_relayPins = new OutputPort[4]; - m_relayPins[0] = new OutputPort(Pins.GPIO_PIN_D7, false); - m_relayPins[1] = new OutputPort(Pins.GPIO_PIN_D6, false); - m_relayPins[2] = new OutputPort(Pins.GPIO_PIN_D5, false); - m_relayPins[3] = new OutputPort(Pins.GPIO_PIN_D4, false); - - // parse config Hashtable and store array list of commands - // the individual relays are stored in an Array List - m_commands = new Hashtable(); - Hashtable config = (Hashtable)_config; - ArrayList relays = config["relays"] as ArrayList; - ParseCommands(relays); - } - - /// - /// Execute RelayCommand given in state object - /// - /// DictionaryEntry object to process on execution - public override void ExecuteControl(object state) - { - // Callback received a RelayCommand struct to execute - var command = (DictionaryEntry)state; - Debug.Print(command.Key.ToString() + "=" + command.Value.ToString()); - m_relayPins[(int)command.Key].Write((bool)command.Value); - } - - /// - /// Parse JSON array of relay config and store for delayed execution - /// - /// JSON formatted list of objects - private void ParseCommands(ArrayList _commands) - { - foreach (Hashtable command in _commands) - { - // parse out details from config - int relayID = Int32.Parse(command["id"].ToString()); - TimeSpan timeOn = GetTimeSpan(command["on"].ToString()); - DictionaryEntry relayOn = new DictionaryEntry(relayID, true); - m_commands.Add(timeOn, relayOn); - if (m_missedExecute) - ExecuteControl(relayOn); - - TimeSpan timeOff = GetTimeSpan(command["off"].ToString()); - DictionaryEntry relayOff = new DictionaryEntry(relayID, false); - m_commands.Add(timeOff, relayOff); - if (m_missedExecute) - ExecuteControl(relayOff); - - } - } - - /// - /// Determine the timespan from 'now' when the given command should be run - /// - /// An ISO8601 formatted time value representing the time of day for execution - /// Timespan when task should be run - private TimeSpan GetTimeSpan(string _time) - { - // Determine when this event should be fired as a TimeSpan - // Assuming ISO8601 time format "hh:mm" - int hours = int.Parse(_time.Substring(0, 2)); - int minutes = int.Parse(_time.Substring(3, 2)); - DateTime now = DateTime.Now; - DateTime timeToRun = new DateTime(now.Year, now.Month, now.Day, hours, minutes, 0); - - // we missed the window, so setup for next run - // Also, mark that we missed the run, so we can execute the command during the parse. - - if (timeToRun < now) - { - timeToRun = timeToRun.AddDays(1); - m_missedExecute = true; - } - - return new TimeSpan((timeToRun - now).Ticks); - } - } +using System; +using System.Collections; +using Controller; +using System.Threading; +using Microsoft.SPOT; +using Microsoft.SPOT.Hardware; +using SecretLabs.NETMF.Hardware.Netduino; +using System.IO; +using System.Text; + +namespace Plugins +{ + /// + /// Control Plugin to manage relays + /// Relays used can be obtained here: http://www.amazon.com/gp/product/B0057OC5WK/ref=oh_details_o00_s00_i01 + /// + public class Relays : ControlPlugin + { + ~Relays() { Dispose(); } + public override void Dispose() { m_relayPins = null; m_commands = null; } + public override bool ImplimentsEventHandler() { return true; } + private CommandData[] m_commands; + private CommandData[] m_ranges; + private const string m_statusFileName = @"\SD\relaystatus.js"; + public override CommandData[] Commands() { return m_commands; } + + /// + /// Iterates through all of the data received and checks it against its stored ranges + /// and then determines weather or not it needs to turn something on or off based on the range values. + /// + /// Object that raised the callback + /// Last reading + public override void EventHandler(object _sender, IPluginData _data) + { + foreach (PluginData _pd in _data.GetData()) + { + foreach (CommandData CMD in m_ranges) + { + if ((_pd.Name == CMD.RangeMetric) && _pd.LastReadSuccess && CMD.Enable) + CheckRange(m_relayPins[CMD.RelayID].Read(),_pd,CMD); + Debug.GC(true); + } + } + } + + /// + /// Used during Command parsing to trigger immediate command execution. + /// Example: during mid-day, the lights should be on, but a reboot of the controller will restart + /// the Netduino, forcing a re-parse of the config file. + /// The timers will be setup to turn on the next day, but they need to be on immediately as well + /// + private bool m_missedExecute = false; + private bool ON = false; + private bool OFF = true; + private OutputPort[] m_relayPins; + public Relays(object _config) + { + // Initialize Relay Pin controls + m_relayPins = new OutputPort[12]; + m_relayPins[0] = new OutputPort(Pins.GPIO_PIN_D4, OFF); + m_relayPins[1] = new OutputPort(Pins.GPIO_PIN_D5, OFF); + m_relayPins[2] = new OutputPort(Pins.GPIO_PIN_D6, OFF); + m_relayPins[3] = new OutputPort(Pins.GPIO_PIN_D7, OFF); + m_relayPins[4] = new OutputPort(Pins.GPIO_PIN_D8, OFF); + m_relayPins[5] = new OutputPort(Pins.GPIO_PIN_D9, OFF); + m_relayPins[6] = new OutputPort(Pins.GPIO_PIN_D10, OFF); + m_relayPins[7] = new OutputPort(Pins.GPIO_PIN_D11, OFF); + //Relays for ph and nutrient pumps. + m_relayPins[8] = new OutputPort(Pins.GPIO_PIN_A2, OFF); + m_relayPins[9] = new OutputPort(Pins.GPIO_PIN_A3, OFF); + m_relayPins[10] = new OutputPort(Pins.GPIO_PIN_A4, OFF); + m_relayPins[11] = new OutputPort(Pins.GPIO_PIN_A5, OFF); + // parse config Hashtable and store array list of commands + // the individual relays are stored in an Array List + Hashtable config = (Hashtable)_config; + ArrayList relays = config["relays"] as ArrayList; + ParseCommands(relays); + } + + /// + /// Execute RelayCommand given in state object + /// + /// DictionaryEntry object to process on execution + public override void ExecuteControl(object state) + { + // Callback received a RelayCommand struct to execute + CommandData command = (CommandData)state; + if (command.Enable) + { + Debug.Print("Setting " + command.RelayName + " to " + (!command.Command).ToString()); + m_relayPins[command.RelayID].Write(command.Command); + UpdateStatusFile(); + } + } + + /// + /// Parse JSON array of relay config and store for delayed execution + /// + /// JSON formatted list of objects + private void ParseCommands(ArrayList _commands) + { + ArrayList CMDs = new ArrayList(); + ArrayList Ranges = new ArrayList(); + foreach (Hashtable command in _commands) + { + try + { + CommandData CMD = new CommandData(); + CommandData CMD_1 = new CommandData(); + // parse out details from config into the CommandData class + CMD.Enable = (command["Enable"].ToString() == "true"); + CMD.TimerType = command["type"].ToString(); + CMD.RelayName = command["name"].ToString(); + CMD.RelayID = int.Parse(command["id"].ToString()); + + CMD_1.Enable = CMD.Enable; + CMD_1.TimerType = CMD.TimerType; + CMD_1.RelayName = CMD.RelayName; + CMD_1.RelayID = CMD.RelayID; + + switch (CMD.TimerType) + { + case "DailyTimer": + CMD.FirstRun = GetTimeSpan(command["on"].ToString()); + CMD.Command = ON; + CMD.RepeatTimeSpan = new TimeSpan(24, 0, 0); + CMDs.Add(CMD); + if (m_missedExecute) + ExecuteControl(CMD); + + CMD_1.RepeatTimeSpan = new TimeSpan(24, 0, 0); + CMD_1.FirstRun = GetTimeSpan(command["off"].ToString()); + CMD_1.Command = OFF; + CMDs.Add(CMD_1); + if (m_missedExecute) + ExecuteControl(CMD_1); + break; + + case "Timer": + CMD.DurationOff = GetTimeSpanFromArrayList(command["DurationOff"] as ArrayList); + CMD.DurationOn = GetTimeSpanFromArrayList(command["DurationOn"] as ArrayList); + //Create On Timer + CMD.FirstRun = CMD.DurationOff; + CMD.RepeatTimeSpan = CMD.DurationOff + CMD.DurationOn; + CMD.Command = ON; + CMDs.Add(CMD); + //Create Off timer + CMD_1.DurationOff = CMD.DurationOff; + CMD_1.DurationOn = CMD.DurationOn; + CMD_1.RepeatTimeSpan = CMD.DurationOff + CMD.DurationOn; + CMD_1.FirstRun = CMD_1.DurationOff + CMD_1.DurationOn; + CMD_1.Command = OFF; + CMDs.Add(CMD_1); + break; + + case "Range": + CMD.RangeMetric = command["RangeMetric"].ToString(); + CMD.RangeMin = double.Parse(command["min"].ToString()); + CMD.RangeMax = double.Parse(command["max"].ToString()); + CMD.Inverted = (command["Inverted"].ToString() == "true"); + CMD.PulseTime = int.Parse(command["PulseTime"].ToString()); + if (CMD.PulseTime > 0) + { + CMD.TimeBetweenPulses = int.Parse(command["PulseSpace"].ToString()); + CMD.NextPulseAfter = DateTime.Now; + } + Ranges.Add(CMD); + break; + } + } + catch (Exception e) + { + Debug.Print(e.Message); + } + finally + { + m_commands = new CommandData[CMDs.Count]; + m_ranges = new CommandData[Ranges.Count]; + for (int i = 0; i < CMDs.Count; i++) + { + m_commands[i] = (CommandData)CMDs[i]; + } + for (int i = 0; i < Ranges.Count; i++) + { + m_ranges[i] = (CommandData)Ranges[i]; + } + Debug.GC(true); + } + } + } + + /// + /// Determine the timespan from 'now' when the given command should be run + /// + /// An ISO8601 formatted time value representing the time of day for execution + /// Timespan when task should be run + private TimeSpan GetTimeSpan(string _time) + { + // Determine when this event should be fired as a TimeSpan + // Assuming ISO8601 time format "hh:mm" + int hours = int.Parse(_time.Substring(0, 2)); + int minutes = int.Parse(_time.Substring(3, 2)); + DateTime now = DateTime.Now; + DateTime timeToRun = new DateTime(now.Year, now.Month, now.Day, hours, minutes, 0); + + // we missed the window, so setup for next run + // Also, mark that we missed the run, so we can execute the command during the parse. + + if (timeToRun < now) + { + timeToRun = timeToRun.AddDays(1); + m_missedExecute = true; + } + else + m_missedExecute = false; + + return new TimeSpan((timeToRun - now).Ticks); + } + + private TimeSpan GetTimeSpanFromArrayList(ArrayList _time) + { + // The Timer Interval is specified in an Arraylist of numbers + //ArrayList interval = config["interval"] as ArrayList; + + //TODO: Double casting since for some reason an explicit cast from a Double + // to an Int doesn't work. It's a boxing issue, as interval[i] returns an object. + // And JSON.cs returns numbers as doubles + int[] times = new int[3]; + for (int i = 0; i < 3; i++) + { + times[i] = (int)(double)_time[i]; + } + return new TimeSpan(times[0], times[1], times[2]); + } + + /// + /// Checks received values for a sensor against ranges that are configured. + /// and decides what to do with the relay thats asigned. + /// + /// Current power state of the relay + /// Values from the plugin\sensor + /// Rang settings object + private void CheckRange(bool PinState, PluginData Sensor, CommandData CMD) + { + switch (CMD.Inverted) + { + case true: + //Inverted + if(Sensor.Value <= CMD.RangeMin) + { + // Reading is below Min + // If the appliance is powered off turn it on + Debug.Print(Sensor.Name + " is Below Minimum."); + if (PinState = OFF) + { + if (CMD.PulseTime > 0) + { + if (CMD.NextPulseAfter <= DateTime.Now) + { + m_relayPins[CMD.RelayID].Write(ON); + Thread.Sleep(CMD.PulseTime); + m_relayPins[CMD.RelayID].Write(OFF); + Debug.Print("Powering On " + CMD.RelayName + " For " + CMD.PulseTime + " MS"); + CMD.NextPulseAfter = DateTime.Now + new TimeSpan(0, CMD.TimeBetweenPulses, 0); + } + else + Debug.Print("Skipping Range. Next available time to pulse is: " + CMD.PulseTime.ToString()); + } + else + { + m_relayPins[CMD.RelayID].Write(ON); + Debug.Print("Powering On " + CMD.RelayName); + } + } + } + else + { + // If the Reading is not below the min, power + // the appliance off if needed. + if(PinState == ON && Sensor.Value >= CMD.RangeMax && CMD.PulseTime == 0) + { + Debug.Print(Sensor.Name + " is Above Max. Powering Off " + CMD.RelayName); + m_relayPins[CMD.RelayID].Write(OFF); + } + } + break; + case false: + //Not Inverted + if (Sensor.Value >= CMD.RangeMax) + { + // Reading is Above Max + // If the appliance is powered off turn it on + Debug.Print(Sensor.Name + " is Above Maximum."); + if (PinState = OFF) + { + if (CMD.PulseTime > 0) + { + if (CMD.NextPulseAfter <= DateTime.Now) + { + m_relayPins[CMD.RelayID].Write(ON); + Thread.Sleep(CMD.PulseTime); + m_relayPins[CMD.RelayID].Write(OFF); + Debug.Print("Powering On " + CMD.RelayName + " For " + CMD.PulseTime + " MS"); + CMD.NextPulseAfter = DateTime.Now + new TimeSpan(0, CMD.TimeBetweenPulses, 0); + } + else + Debug.Print("Skipping Range. Next available time to pulse is: " + CMD.PulseTime.ToString()); + } + else + { + m_relayPins[CMD.RelayID].Write(ON); + Debug.Print("Powering On " + CMD.RelayName); + } + } + } + else + { + // If the Reading is not below the min, power + // the appliance off if needed. + if (PinState == ON && Sensor.Value <= CMD.RangeMin && CMD.PulseTime == 0) + { + Debug.Print(Sensor.Name + " is Below Min. Powering Off " + CMD.RelayName); + m_relayPins[CMD.RelayID].Write(OFF); + } + } + break; + } + } + + /// + /// Updates relaystatus.js on the sd card with the status of all of the relays. + /// + private void UpdateStatusFile() + { + string statusString = "var rs={"; + try + { + string State = ""; + foreach(CommandData CMD in m_commands) + { + if (m_relayPins[CMD.RelayID].Read() == ON) State = "ON"; else State = "OFF"; + if (!statusString.Contains(CMD.RelayName)) + statusString += '"' + CMD.RelayName + '"' + ':' + '"' + State + '"' + ','; + } + foreach (CommandData CMD in m_ranges) + { + if (m_relayPins[CMD.RelayID].Read() == ON) State = "ON"; else State = "OFF"; + if (!statusString.Contains(CMD.RelayName)) + statusString += '"' + CMD.RelayName + '"' + ':' + '"' + State + '"' + ','; + } + statusString += '"' + "Time" + '"' + ':' + '"' + DateTime.Now.ToString("s") + '"' + '}'+ ';'; + // write relaystatus.js back down to fs, including the var declaration + byte[] statusBytes = Encoding.UTF8.GetBytes(statusString); + using (FileStream fs = new FileStream(m_statusFileName, FileMode.Create)) + { + fs.Write(statusBytes, 0, statusBytes.Length); + } + } + catch (Exception e) + { + Debug.Print(e.Message); + } + + } + } } \ No newline at end of file diff --git a/Plugin-Relays/Relays.htm b/Plugin-Relays/Relays.htm index df73de8..8d651c2 100644 --- a/Plugin-Relays/Relays.htm +++ b/Plugin-Relays/Relays.htm @@ -1 +1,643 @@ -
Relays

Controls the first four plugs on the power bar.

#1
#2
#3
#4
\ No newline at end of file +
+ Relays + + +

Controls the first eight plugs on the power bar.

+ +
+ #1 + + +
+ + +
+ + +
+
+ + +
+ + +
+
+
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+
+
+ + +
+
+
+
+
+ +
+ #2 + + +
+ + +
+ + +
+
+ + +
+ + +
+
+
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+
+
+ + +
+
+
+
+
+ +
+ #3 + + +
+ + +
+ + +
+
+ + +
+ + +
+
+
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+
+
+ + +
+
+
+
+
+ +
+ #4 + + +
+ + +
+ + +
+
+ + +
+ + +
+
+
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+
+
+ + +
+
+
+
+
+ +
+ #5 + + +
+ + +
+ + +
+
+ + +
+ + +
+
+
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+
+
+ + +
+
+
+
+
+ +
+ #6 + + +
+ + +
+ + +
+
+ + +
+ + +
+
+
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+
+
+ + +
+
+
+
+
+ +
+ #7 + + +
+ + +
+ + +
+
+ + +
+ + +
+
+
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+
+
+ + +
+
+
+
+
+ +
+ #8 + + +
+ + +
+ + +
+
+ + +
+ + +
+
+
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+
+
+ + +
+
+
+
+
+ +
+ #9 + + +
+ + +
+ + +
+
+ + +
+ + +
+
+
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+
+
+ + +
+
+
+
+
+ +
+ #10 + + +
+ + +
+ + +
+
+ + +
+ + +
+
+
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+
+
+ + +
+
+
+
+
+ +
+ #11 + + +
+ + +
+ + +
+
+ + +
+ + +
+
+
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+
+
+ + +
+
+
+
+
+ +
+ #12 + + +
+ + +
+ + +
+
+ + +
+ + +
+
+
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+
+
+ + +
+
+
+
+
+ +
\ No newline at end of file diff --git a/Plugin-Relays/relays.js b/Plugin-Relays/relays.js index db338fc..acf606f 100644 --- a/Plugin-Relays/relays.js +++ b/Plugin-Relays/relays.js @@ -1,92 +1,244 @@ -var relayInit = function() { - var pluginEnabled = (config.config.control.Relays.enabled === 'true'); - var relayEnable = $('#PRE'); - relayEnable.attr('checked', pluginEnabled); - relayEnable.click(function() { - config.config.control.Relays.enabled = this.checked ? 'true' : 'false'; - $(this).button("option","label", this.checked ? "Enabled" : "Disabled"); - }); - relayEnable.button({ label: (pluginEnabled ? "Enabled" : "Disabled") }).button('refresh'); - - $('div[id^="rel"]').each(function() { - var relayNumber = $(this).attr("id"); - relayNumber = relayNumber.charAt(relayNumber.length-1); - var onValue = ToTicks(config.config.control.Relays.relays[relayNumber-1].on); - var offValue = ToTicks(config.config.control.Relays.relays[relayNumber-1].off); - var onButton = $('input[id="en'+relayNumber+'"]'); - $('div[id="ti'+relayNumber+'"]').html(ToTime(onValue)+" - " + ToTime(offValue)); - $(this).slider({ - range: true, - min: 0, - max: 1440, - step: 15, - values:[onValue,offValue], - slide: function(event, ui) { - SetTime(ui, relayNumber); - }, - change: function(event, ui) { - SetTime(ui, relayNumber); - } - }); - onButton.attr('checked', isRelayOn(onValue, offValue)); - // AJAX call to Netduino to toggle relay status - onButton.click(function() { $(this).button("option","label", this.checked ? "On" : "Off"); - var button = this; - $.ajax({ - url: '/Relays', - type: 'GET', - dataType: 'json', - data: { - relay: relayNumber-1, //Relays are 0 indexed on the control system. - status: button.checked, - }, - success: function(result) { - - } - }); - }); - onButton.button({ label: (isRelayOn(onValue, offValue) ? "On" : "Off") }).button('refresh'); - }); -}; - -function SetTime(ui, relay) { - var start_time = ToTime(ui.values[0]); - var end_time = ToTime(ui.values[1]); - config.config.control.Relays.relays[relay-1].on = GetHours(ui.values[0])+''+GetMinutes(ui.values[0]); - config.config.control.Relays.relays[relay-1].off = GetHours(ui.values[1])+''+GetMinutes(ui.values[1]); - $('div[id="ti'+relay+'"]').html(start_time +" - " + end_time); -} - -function ToTime(ticks) { - var hours = GetHours(ticks); - var minutes = GetMinutes(ticks); - return ZeroPad(hours,2)+":"+ZeroPad(minutes,2); -} - -function GetHours(ticks) { - return Math.floor(ticks / 60); -} - -function GetMinutes(ticks) { - var hours = GetHours(ticks); - return ticks - (hours * 60); -} - -function ToTicks(time) { - var length = time.toString().length; - var hours = length == 3 ? time.toString().substring(0,1) : time.toString().substring(0,2); - var minutes = time.toString().substring(length-2); - return (hours*60)+Number(minutes); -} - -function isRelayOn(start, end) { - var now = new Date(); - var relayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate(), GetHours(start), GetMinutes(start)); - var relayEnd = new Date(now.getFullYear(), now.getMonth(), now.getDate(), GetHours(end), GetMinutes(end)); - return ( (now >= relayStart) && (now < relayEnd) ); -} - -function ZeroPad(num, places) { - var zero = places - num.toString().length+1; - return Array(+(zero > 0 && zero)).join("0") + num; +var RelaysInit = function () { + var pluginEnabled = (config.config.control.Relays.enabled == 'true'); + var relayEnable = $('#PRE'); + relayEnable.attr('checked', pluginEnabled); + relayEnable.click(function () { + config.config.control.Relays.enabled = this.checked ? 'true' : 'false'; + $(this).button("option", "label", this.checked ? "Enabled" : "Disabled"); + }); + relayEnable.button({ label: (pluginEnabled ? "Enabled" : "Disabled") }).button('refresh'); + + $('div[class^="conditionalFields"]').each(function () { + var relayNumber = parseInt($(this).attr("class").replace("conditionalFields", "")); + var Enable = (config.config.control.Relays.relays[relayNumber - 1].Enable === 'true'); + var RelayType = config.config.control.Relays.relays[relayNumber - 1].type; + var Name = config.config.control.Relays.relays[relayNumber - 1].name; + var RelayTypeSelectBox = document.getElementById("Relay" + relayNumber + "Type"); + var RelayNameField = $("#Relay" + relayNumber + "Name"); + + //Populate RangeMetric Boxes + var RangeMetric = document.getElementById("Relay" + relayNumber + "RangeMetric"); + for (var prop in aq) { + if (aq.hasOwnProperty(prop)) { + //alert("prop: " + prop + " value: " + aq[prop]) + if (!(prop === "time")) + RangeMetric.options[RangeMetric.options.length] = new Option(prop, prop); + } + } + + //Set what happens when name is changed + RelayNameField.change(function () { + config.config.control.Relays.relays[relayNumber - 1].name = RelayNameField.val(); + }); + + //Set what happens when RelayType is changed. + RelayTypeSelectBox.onchange = function () { + RelayTypeChanged(relayNumber); + }; + + //Set what happens when the enable/disable button is clicked. + var rEnable = $("#Relay" + relayNumber + "Enable"); + rEnable.attr('checked', Enable); + rEnable.click(function () { + Enable = this.checked + $(this).button("option", "label", this.checked ? "Enabled" : "Disabled"); + EnableClicked(relayNumber, Enable); + }); + rEnable.button({ label: (Enable ? "Enabled" : "Disabled") }).button('refresh'); + EnableClicked(relayNumber, Enable); + + //Populate name field + RelayNameField.val(Name); + + //Populate the RangeType Box. + for (var i = 0; i < RelayTypeSelectBox.options.length; i++) { + if (RelayTypeSelectBox.options[i].value === RelayType) { + RelayTypeSelectBox.selectedIndex = i; + RelayTypeSelectBox.onchange(); + break; + } + } + }); +}; + + +function EnableClicked(relayNumber, Enable) { + if (Enable) { + $('.conditionalFields' + relayNumber).show(); + } else { + $('.conditionalFields' + relayNumber).hide(); + } + config.config.control.Relays.relays[relayNumber - 1].Enable = Enable ? 'true' : 'false'; +} + +function RelayTypeChanged(relayNumber) { + var RelayType = document.getElementById("Relay" + relayNumber + "Type").value; + config.config.control.Relays.relays[relayNumber - 1].type = RelayType + + var TimerFields = $('.RelayTimerFields' + relayNumber); + var DailyTimerFields = $('.RelayDailyTimerFields' + relayNumber); + var RangeFields = $('.RelayRangeFields' + relayNumber); + var onButton = $('input[id="en' + relayNumber + '"]'); + var TimesLabel = $('div[id="ti' + relayNumber + '"]') + var TimeSlider = $('div[id="rel' + relayNumber + '"]') + var onDuration = $('input[id="Relay' + relayNumber + 'DurationOn"]'); + var OffDuration = $('input[id="Relay' + relayNumber + 'DurationOff"]'); + var RangeMetric = $('select[id="Relay' + relayNumber + 'RangeMetric"]'); + var RangeMax = $('input[id="Relay' + relayNumber + 'RangeMax"]'); + var RangeMin = $('input[id="Relay' + relayNumber + 'RangeMin"]'); + var RangePulseTime = $('input[id="Relay' + relayNumber + 'PulseTime"]'); + var RangePulseSpace = $('input[id="Relay' + relayNumber + 'PulseSpace"]'); + var chkInverted = $("#Relay" + relayNumber + "Inverted"); + + switch (RelayType) { + case "DailyTimer": + DailyTimerFields.show(); + RangeFields.hide(); + TimerFields.hide(); + + //assign data to all fields for this relay and this relay type and then show them or add them. + if (config.config.control.Relays.relays[relayNumber - 1].on == undefined) config.config.control.Relays.relays[relayNumber - 1].on = "00:00"; + if (config.config.control.Relays.relays[relayNumber - 1].off == undefined) config.config.control.Relays.relays[relayNumber - 1].off = "03:00"; + + var onValue = ToTicks(config.config.control.Relays.relays[relayNumber - 1].on); + var offValue = ToTicks(config.config.control.Relays.relays[relayNumber - 1].off); + + TimesLabel.html(ToTime(onValue) + " - " + ToTime(offValue)); + TimeSlider.slider({ + range: true, + min: 0, + max: 1440, + step: 15, + values: [onValue, offValue], + slide: function (event, ui) { + SetTime(ui, relayNumber); + }, + change: function (event, ui) { + SetTime(ui, relayNumber); + } + }); + onButton.attr('checked', isRelayOn(onValue, offValue)); + // AJAX call to Netduino to toggle relay status + onButton.click(function () { + $(this).button("option", "label", this.checked ? "On" : "Off"); + var button = this; + $.ajax({ + url: '/Relays', + type: 'GET', + dataType: 'json', + data: { + relay: relayNumber - 1, //Relays are 0 indexed on the control system. + status: button.checked, + }, + success: function (result) { + + } + }); + }); + onButton.button({ label: (isRelayOn(onValue, offValue) ? "On" : "Off") }).button('refresh'); + break; + case "Timer": + DailyTimerFields.hide(); + RangeFields.hide(); + TimerFields.show(); + + //Set default valuse if none are set. + if (config.config.control.Relays.relays[relayNumber - 1].DurationOn === undefined) + config.config.control.Relays.relays[relayNumber - 1].DurationOn = [0, 15, 0]; + if (config.config.control.Relays.relays[relayNumber - 1].DurationOff === undefined) + config.config.control.Relays.relays[relayNumber - 1].DurationOff = [0, 15, 0]; + + //load values + onDuration.val(config.config.control.Relays.relays[relayNumber - 1].DurationOn); + OffDuration.val(config.config.control.Relays.relays[relayNumber - 1].DurationOff); + + //Save values on change + onDuration.change(function () { + config.config.control.Relays.relays[relayNumber - 1].DurationOn = onDuration.val(); + }); + + OffDuration.change(function () { + config.config.control.Relays.relays[relayNumber - 1].DurationOff = offDuration.val(); + }); + + break; + case "Range": + DailyTimerFields.hide(); + RangeFields.show(); + TimerFields.hide(); + //Create a button for the inverted checkbox and set it. + var Inverted = (config.config.control.Relays.relays[relayNumber - 1].Inverted === 'true'); + chkInverted.attr('checked', Inverted); + chkInverted.click(function () { + config.config.control.Relays.relays[relayNumber - 1].Inverted = this.checked ? 'true' : 'false'; + $(this).button("option", "label", this.checked ? "Inverted" : "Non-Inverted"); + }); + chkInverted.button({ label: (Inverted ? "Inverted" : "Non-Inverted") }).button('refresh'); + //Set Values for Range + RangeMetric.val(config.config.control.Relays.relays[relayNumber - 1].RangeMetric); + RangeMax.val(config.config.control.Relays.relays[relayNumber - 1].max); + RangeMin.val(config.config.control.Relays.relays[relayNumber - 1].min); + RangePulseTime.val(config.config.control.Relays.relays[relayNumber - 1].PulseTime); + RangePulseSpace.val(config.config.control.Relays.relays[relayNumber - 1].PulseSpace); + + //Update Config variable if settings are changed. + RangeMetric.change(function () { + config.config.control.Relays.relays[relayNumber - 1].RangeMetric = RangeMetric.val(); + }); + RangeMax.change(function () { + config.config.control.Relays.relays[relayNumber - 1].max = RangeMax.val(); + }); + RangeMin.change(function () { + config.config.control.Relays.relays[relayNumber - 1].max = RangeMin.val(); + }); + RangePulseTime.change(function () { + config.config.control.Relays.relays[relayNumber - 1].PulseTime = RangePulseTime.val(); + }); + RangePulseSpace.change(function () { + config.config.control.Relays.relays[relayNumber - 1].PulseSpace = RangePulseSpace.val(); + }); + break; + } +} + +function SetTime(ui, relay) { + var start_time = ToTime(ui.values[0]); + var end_time = ToTime(ui.values[1]); + config.config.control.Relays.relays[relay - 1].on = GetHours(ui.values[0]) + '' + GetMinutes(ui.values[0]); + config.config.control.Relays.relays[relay - 1].off = GetHours(ui.values[1]) + '' + GetMinutes(ui.values[1]); + $('div[id="ti' + relay + '"]').html(start_time + " - " + end_time); +} + +function ToTime(ticks) { + var hours = GetHours(ticks); + var minutes = GetMinutes(ticks); + return ZeroPad(hours, 2) + ":" + ZeroPad(minutes, 2); +} + +function GetHours(ticks) { + return Math.floor(ticks / 60); +} + +function GetMinutes(ticks) { + var hours = GetHours(ticks); + return ticks - (hours * 60); +} + +function ToTicks(time) { + var length = time.toString().length; + var hours = length == 3 ? time.toString().substring(0, 1) : time.toString().substring(0, 2); + var minutes = time.toString().substring(length - 2); + return (hours * 60) + Number(minutes); +} + +function isRelayOn(start, end) { + var now = new Date(); + var relayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate(), GetHours(start), GetMinutes(start)); + var relayEnd = new Date(now.getFullYear(), now.getMonth(), now.getDate(), GetHours(end), GetMinutes(end)); + return ((now >= relayStart) && (now < relayEnd)); +} + +function ZeroPad(num, places) { + var zero = places - num.toString().length + 1; + return Array(+(zero > 0 && zero)).join("0") + num; } \ No newline at end of file diff --git a/Plugin-Temperature/DS18B20.cs b/Plugin-Temperature/DS18B20.cs new file mode 100644 index 0000000..0b94c4d --- /dev/null +++ b/Plugin-Temperature/DS18B20.cs @@ -0,0 +1,167 @@ +using System; + +using Microsoft.SPOT; +using Microsoft.SPOT.Hardware; + +namespace ThreelnDotOrg.NETMF.Hardware +{ + public class DS18B20 : IDisposable + { + private OutputPort m_op; + private OneWire m_ow; + private OneWireBus.Device m_dev; + + public DS18B20(Cpu.Pin pin) : this(pin, null) { } + + public DS18B20(Cpu.Pin pin, OneWireBus.Device dev) + { + m_op = new OutputPort(pin, false); + m_ow = new OneWire(m_op); + + if( dev == null ) + { + var devs = OneWireBus.Scan(m_ow, OneWireBus.Family.DS18B20); + + if( devs == null || devs.Length < 1 ) + throw new InvalidOperationException("No DS18B20 devices found on OneWire bus"); + + dev = devs[0]; + } + + m_dev = dev; + } + + public DS18B20(OneWire ow) : this(ow, null) { } + + public DS18B20(OneWire ow, OneWireBus.Device dev) + { + m_ow = ow; + + if( dev == null ) + { + var devs = OneWireBus.Scan(ow, OneWireBus.Family.DS18B20); + + if( devs == null || devs.Length < 1 ) + throw new InvalidOperationException("No DS18B20 devices found on OneWire bus"); + + dev = devs[0]; + } + + m_dev = dev; + } + + public void Dispose() + { + if( m_op != null ) + m_op.Dispose(); + } + + public float ConvertAndReadTemperature() + { + var data = 0L; + + // if reset finds no devices, just return 0 + if( m_ow.TouchReset() == 0 ) + return 0; + + // address the device + m_ow.WriteByte(Command.MatchROM); + WriteBytes(m_dev.Address); + + // tell the device to start temp conversion + m_ow.WriteByte(Command.StartTemperatureConversion); + + // wait for as long as it takes to do the temp conversion, + // data sheet says ~750ms + while( m_ow.ReadByte() == 0 ) + System.Threading.Thread.Sleep(1); + + // reset the bus + m_ow.TouchReset(); + + // address the device + m_ow.WriteByte(Command.MatchROM); + WriteBytes(m_dev.Address); + + // read the data from the sensor + m_ow.WriteByte(Command.ReadScratchPad); + + // read the two bytes of data + data = m_ow.ReadByte(); // LSB + data |= (ushort)(m_ow.ReadByte() << 8); // MSB + + // reset the bus, we don't want more data than that + m_ow.TouchReset(); + + // returns C + // F would be: (float)((1.80 * (data / 16.00)) + 32.00); + return (float)data / 16f; + } + + public void StartConversion() + { + // if reset finds no devices, just return 0 + if( m_ow.TouchReset() == 0 ) + return; + + // address the device + m_ow.WriteByte(Command.MatchROM); + WriteBytes(m_dev.Address); + + // tell the device to start temp conversion + m_ow.WriteByte(Command.StartTemperatureConversion); + } + + public float ReadTemperature() + { + var data = 0L; + + // reset the bus + m_ow.TouchReset(); + + // address the device + m_ow.WriteByte(Command.MatchROM); + WriteBytes(m_dev.Address); + + // read the data from the sensor + m_ow.WriteByte(Command.ReadScratchPad); + + // read the two bytes of data + data = m_ow.ReadByte(); // LSB + data |= (ushort)(m_ow.ReadByte() << 8); // MSB + + // reset the bus, we don't want more data than that + m_ow.TouchReset(); + + // returns C + // F would be: (float)((1.80 * (data / 16.00)) + 32.00); + return (float)data / 16f; + } + + public static float ToFahrenheit(float tempC) + { + return (9f / 5f) * tempC + 32f; + } + + private void WriteBytes(byte[] data) + { + for( var i = 0; i < data.Length; i++ ) + m_ow.WriteByte(data[i]); + } + + private static class Command + { + public const byte SearchROM = 0xF0; + public const byte ReadROM = 0x33; + public const byte MatchROM = 0x55; + public const byte SkipROM = 0xCC; + public const byte AlarmSearch = 0xEC; + public const byte StartTemperatureConversion = 0x44; + public const byte ReadScratchPad = 0xBE; + public const byte WriteScratchPad = 0x4E; + public const byte CopySratchPad = 0x48; + public const byte RecallEEPROM = 0xB8; + public const byte ReadPowerSupply = 0xB4; + } + } +} diff --git a/Plugin-Temperature/OneWireBus.cs b/Plugin-Temperature/OneWireBus.cs new file mode 100644 index 0000000..688326e --- /dev/null +++ b/Plugin-Temperature/OneWireBus.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections; + +using Microsoft.SPOT; +using Microsoft.SPOT.Hardware; + +namespace ThreelnDotOrg.NETMF.Hardware +{ + public static class OneWireBus + { + public class Device + { + public byte[] Address { get; private set; } + + public Family Family { get; private set; } + + internal Device(byte[] addr) + { + Address = addr; + + switch( addr[0] ) + { + case 0x10: + Family = OneWireBus.Family.DS18S20; + break; + + case 0x28: + Family = OneWireBus.Family.DS18B20; + break; + + default: + Family = OneWireBus.Family.Unknown; + break; + } + } + } + + public enum Family : byte + { + Unknown = 0x00, + DS18S20 = 0x10, + DS18B20 = 0x28, + } + + public static Device[] Scan(OneWire ow, params Family[] includeFamilies) + { + var list = new ArrayList(); + var all = false; + var devs = ow.FindAllDevices(); + + if( includeFamilies != null ) + { + foreach( var f in includeFamilies ) + { + if( f == Family.Unknown ) + all = true; + } + } + + foreach( byte[] da in devs ) + { + if( includeFamilies == null || includeFamilies.Length == 0 || all ) + { + list.Add(new Device(da)); + } + else + { + foreach( var f in includeFamilies ) + { + if( da[0] == (byte)f ) + list.Add(new Device(da)); + } + } + } + + return (Device[])list.ToArray(typeof(Device)); + } + + public static Device[] Scan(Cpu.Pin pin, params Family[] includeFamilies) + { + using( var op = new OutputPort(pin, false) ) + return Scan(new OneWire(op), includeFamilies); + } + } +} diff --git a/Plugin-Temperature/Plugin-Temperature.csproj b/Plugin-Temperature/Plugin-Temperature.csproj index b07b065..c1945d5 100644 --- a/Plugin-Temperature/Plugin-Temperature.csproj +++ b/Plugin-Temperature/Plugin-Temperature.csproj @@ -1,72 +1,75 @@ - - - - Temperature - Library - Plugins - {b69e3092-b931-443c-abe7-7e7b65f2a37f};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 9.0.21022 - 2.0 - {79EFAD9E-C3E1-4D3F-993E-7CFF382A1C29} - v4.2 - $(MSBuildExtensionsPath32)\Microsoft\.NET Micro Framework\ - - - true - full - false - bin\Debug\ - TRACE;DEBUG;TRACE,MF_FRAMEWORK_VERSION_V4_2 - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - true - bin\Emulator\ - DEBUG;TRACE,MF_FRAMEWORK_VERSION_V4_2 - true - full - AnyCPU - false - prompt - true - true - - - OnOutputUpdated - - - - - - - - - {117848BD-213C-4AE4-8F58-D27F14DAA534} - Controller - - - - - - - - - - - - - - - copy $(TargetDir)le\$(TargetName).pe g:\plugins -copy $(ProjectDir)$(TargetName).htm g:\plugins - + + + + Temperature + Library + Plugins + {b69e3092-b931-443c-abe7-7e7b65f2a37f};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 9.0.21022 + 2.0 + {79EFAD9E-C3E1-4D3F-993E-7CFF382A1C29} + v4.2 + $(MSBuildExtensionsPath32)\Microsoft\.NET Micro Framework\ + + + true + full + false + bin\Debug\ + TRACE;DEBUG;TRACE,MF_FRAMEWORK_VERSION_V4_2 + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + bin\Emulator\ + DEBUG;TRACE,MF_FRAMEWORK_VERSION_V4_2 + true + full + AnyCPU + false + prompt + true + true + + + OnOutputUpdated + + + + + + + + + + + {117848BD-213C-4AE4-8F58-D27F14DAA534} + Controller + + + + + + + + + + + + + + copy $(TargetDir)le\$(TargetName).pe g:\plugins\ + +copy $(ProjectDir)$(TargetName).htm g:\plugins\ +copy $(ProjectDir)$(TargetName).js g:\files\$(TargetName).min.js + \ No newline at end of file diff --git a/Plugin-Temperature/Temperature.cs b/Plugin-Temperature/Temperature.cs index c9b13e6..874d9d8 100644 --- a/Plugin-Temperature/Temperature.cs +++ b/Plugin-Temperature/Temperature.cs @@ -1,105 +1,108 @@ -using Controller; -using Microsoft.SPOT; -using SecretLabs.NETMF.Hardware; -using SecretLabs.NETMF.Hardware.NetduinoPlus; -using System.Collections; -using System; - - -namespace Plugins -{ - public class TempData : IPluginData - { - private float m_value; - public float GetValue() { return m_value; } - public void SetValue(float _value) { m_value = _value; } - public string DataUnits() { return "C"; } - public ThingSpeakFields DataType() { return ThingSpeakFields.Temperature; } - } - - /// - /// 10K Thermistor, available at http://www.adafruit.com/products/372 - /// - public class Temperature : InputPlugin - { - private const int SeriesResistor = 10000; - private const int ThermistorNominal = 10000; - private const int TemperatureNominal = 25; - private const int BetaCoefficient = 3950; - private TimeSpan m_timerInterval; - - public Temperature() { } - public Temperature(object _config) : base() - { - Hashtable config = (Hashtable)_config; - // The Timer Interval is specified in an Arraylist of numbers - ArrayList interval = config["interval"] as ArrayList; - - //TODO: Double casting since for some reason an explicit cast from a Double - // to an Int doesn't work. It's a boxing issue, as interval[i] returns an object. - // And JSON.cs returns numbers as doubles - int[] times = new int[3]; - for (int i = 0; i < 3; i++) { times[i] = (int)(double)interval[i]; } - m_timerInterval = new TimeSpan(times[0], times[1], times[2]); - } - - ~Temperature() { Dispose(); } - public override void Dispose() { } - - public override TimeSpan TimerInterval { get { return m_timerInterval; } } - public IPluginData GetData() { return null; } - - // Temperature doesn't need the Output Handler - public override void EventHandler(object sender, IPluginData data) - { - throw new System.NotImplementedException(); - } - - public override void TimerCallback(object state) - { - Debug.Print("Temperature Callback"); - TempData tempData = new TempData(); - // get current temperature - tempData.SetValue(CalculateTemperature()); - - Debug.Print("Temperature = "+tempData.GetValue().ToString()); - //Timer Callbacks receive a Delegate in the state object - InputDataAvailable ida = (InputDataAvailable)state; - - // call out to the delegate with expected value - ida(tempData); - } - - /// - /// Calculate Temperature value - /// - /// Float value of current Temperature reading - /// Assuming AREF of 3.3v, the default for Rev. B Netduino Plus boards. - /// It's an internal value, no feed to AREF required. - /// Using code tutorial from adafruit http://www.ladyada.net/learn/sensors/thermistor.html - private float CalculateTemperature() - { - AnalogInput ain = new AnalogInput(Pins.GPIO_PIN_A0); - - // take 10 readings to even out the noise - float average = 0.0F; - for (int i = 0; i < 10; i++) { average += ain.Read(); } - average /= 10; - - // convert to a resistance - average = 1023 / average - 1; - average = SeriesResistor / average; - - // apply steinhart - float tempValue = average / ThermistorNominal; - tempValue = Controller.Math.Log(tempValue); - tempValue /= BetaCoefficient; - tempValue += 1.0F / (TemperatureNominal + 273.15F); - tempValue = 1.0F / tempValue; - tempValue -= 273.15F; - - ain.Dispose(); - return tempValue; - } - } +using Controller; +using Microsoft.SPOT; +using SecretLabs.NETMF.Hardware.Netduino; +using System.Collections; +using System; +using Microsoft.SPOT.Hardware; +using ThreelnDotOrg.NETMF.Hardware; + + +namespace Plugins +{ + public class TempData : IPluginData + { + private PluginData[] _PluginData; + public PluginData[] GetData() { return _PluginData; } + public void SetData(PluginData[] _value) { _PluginData = _value; } + } + + /// + /// DS18B20 Digital Temp Sensor: https://www.sparkfun.com/products/11050 + /// + public class Temperature : InputPlugin + { + private TimeSpan m_timerInterval; + public override bool ImplimentsEventHandler() { return false; } + + + public Temperature() { } + public Temperature(object _config) : base() + { + Hashtable config = (Hashtable)_config; + // The Timer Interval is specified in an Arraylist of numbers + ArrayList interval = config["interval"] as ArrayList; + + //TODO: Double casting since for some reason an explicit cast from a Double + // to an Int doesn't work. It's a boxing issue, as interval[i] returns an object. + // And JSON.cs returns numbers as doubles + int[] times = new int[3]; + for (int i = 0; i < 3; i++) { times[i] = (int)(double)interval[i]; } + m_timerInterval = new TimeSpan(times[0], times[1], times[2]); + } + + ~Temperature() { Dispose(); } + public override void Dispose() { } + + public override TimeSpan TimerInterval { get { return m_timerInterval; } } + public IPluginData GetData() { return null; } + + // Temperature doesn't need the Output Handler + public override void EventHandler(object sender, IPluginData data) + { + throw new System.NotImplementedException(); + } + + public override void TimerCallback(object state) + { + TempData tempData = new TempData(); + // get current temperature + tempData.SetData(CalculateTemperature()); + + foreach (PluginData pd in tempData.GetData()) + { + Debug.Print(pd.Name + " = " + pd.Value.ToString()); + } + + //Timer Callbacks receive a Delegate in the state object + InputDataAvailable ida = (InputDataAvailable)state; + + // call out to the delegate with expected value + ida(tempData); + } + + /// + /// Obtain Temperature value + /// + /// PluginData Array + /// For use with the DS18B20 Temperature sensor + private PluginData[] CalculateTemperature() + { + PluginData[] _PluginData = new PluginData[1]; + _PluginData[0] = new PluginData(); + bool ReadSuccess = true; + double temp = 0.0; + try + { + DS18B20 t = new DS18B20(Pins.GPIO_PIN_A0); + temp = t.ConvertAndReadTemperature(); + //float tempf = temp / 5 * 9 + 32; + t.Dispose(); + } + catch (Exception e) + { + Debug.Print(e.Message); + ReadSuccess = false; + } + finally + { + _PluginData[0].Name = "Temperature"; + _PluginData[0].UnitOFMeasurment = "C"; + _PluginData[0].Value = temp; + _PluginData[0].ThingSpeakFieldID = 1; + _PluginData[0].LastReadSuccess = ReadSuccess; + } + + return _PluginData; + } + } } \ No newline at end of file diff --git a/Plugin-Temperature/temperature.js b/Plugin-Temperature/temperature.js index c0005bf..9d6dc85 100644 --- a/Plugin-Temperature/temperature.js +++ b/Plugin-Temperature/temperature.js @@ -1,4 +1,4 @@ -var temperatureInit = function () { +var TemperatureInit = function () { var pluginEnabled = (config.config.input.Temperature.enabled === 'true'); var temperatureEnable = $('#PTE'); temperatureEnable.attr('checked', pluginEnabled); diff --git a/Plugin-Thingspeak/Plugin-Thingspeak.csproj b/Plugin-Thingspeak/Plugin-Thingspeak.csproj index f931525..b07adc8 100644 --- a/Plugin-Thingspeak/Plugin-Thingspeak.csproj +++ b/Plugin-Thingspeak/Plugin-Thingspeak.csproj @@ -1,69 +1,71 @@ - - - - Thingspeak - Library - Plugins - {b69e3092-b931-443c-abe7-7e7b65f2a37f};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 9.0.21022 - 2.0 - {94EB2D77-53BC-4086-BD6F-9F400C1FD13F} - v4.2 - $(MSBuildExtensionsPath32)\Microsoft\.NET Micro Framework\ - - - true - full - false - bin\Debug\ - TRACE;DEBUG;TRACE,MF_FRAMEWORK_VERSION_V4_2 - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - true - bin\Emulator\ - DEBUG;TRACE,MF_FRAMEWORK_VERSION_V4_2 - true - full - AnyCPU - false - prompt - true - true - - - OnOutputUpdated - - - - - - - - - - - - - {117848BD-213C-4AE4-8F58-D27F14DAA534} - Controller - - - - - - - - copy $(TargetDir)le\$(TargetName).pe g:\plugins -copy $(ProjectDir)$(TargetName).htm g:\plugins - + + + + Thingspeak + Library + Plugins + {b69e3092-b931-443c-abe7-7e7b65f2a37f};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 9.0.21022 + 2.0 + {94EB2D77-53BC-4086-BD6F-9F400C1FD13F} + v4.2 + $(MSBuildExtensionsPath32)\Microsoft\.NET Micro Framework\ + + + true + full + false + bin\Debug\ + TRACE;DEBUG;TRACE,MF_FRAMEWORK_VERSION_V4_2 + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + bin\Emulator\ + DEBUG;TRACE,MF_FRAMEWORK_VERSION_V4_2 + true + full + AnyCPU + false + prompt + true + true + + + OnOutputUpdated + + + + + + + + + + + + + {117848BD-213C-4AE4-8F58-D27F14DAA534} + Controller + + + + + + + + copy $(TargetDir)le\$(TargetName).pe g:\plugins\ + +copy $(ProjectDir)$(TargetName).htm g:\plugins\ +copy $(ProjectDir)$(TargetName).js g:\files\$(TargetName).min.js + \ No newline at end of file diff --git a/Plugin-Thingspeak/Thingspeak.cs b/Plugin-Thingspeak/Thingspeak.cs index 7f8ba8d..f19d1df 100644 --- a/Plugin-Thingspeak/Thingspeak.cs +++ b/Plugin-Thingspeak/Thingspeak.cs @@ -1,78 +1,130 @@ -using System; -using System.Collections; -using System.Net; -using System.Net.Sockets; -using System.Text; -using Controller; -using Microsoft.SPOT; - -namespace Plugins -{ - public class Thingspeak : OutputPlugin - { - ~Thingspeak() { Dispose(); } - public override void Dispose() { } - private string m_httpPost; - private const string m_thingSpeakIP = "184.106.153.149"; - - public Thingspeak() { } - - public Thingspeak(object _config) - { - Hashtable config = (Hashtable)_config; - m_httpPost = "POST /update HTTP/1.1\nHost: api.thingspeak.com\nConnection: close\nX-THINGSPEAKAPIKEY: "; - m_httpPost += config["writeapi"].ToString() + "\n"; - m_httpPost += "Content-Type: application/x-www-form-urlencoded\nContent-Length: "; - } - - private static Socket ConnectSocket(String server, Int32 port) - { - // Get server's IP address. - IPHostEntry hostEntry = Dns.GetHostEntry(server); - - // Create socket and connect to the server's IP address and port - Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - socket.Connect(new IPEndPoint(hostEntry.AddressList[0], port)); - return socket; - } - - public override void EventHandler(object _sender, IPluginData _data) - { - // build field string from _data and append to the post string - string fieldData = "field"+(uint)_data.DataType()+"="+_data.GetValue().ToString("F"); - - // add content length to post string, then data - string postString = m_httpPost + fieldData.Length + "\n\n" + fieldData; - - //Open the Socket and post the data - // create required networking parameters - - using (Socket thingSpeakSocket = ConnectSocket(m_thingSpeakIP, 80)) - { - Byte[] sendBytes = Encoding.UTF8.GetBytes(postString); - thingSpeakSocket.Send(sendBytes, sendBytes.Length, 0); - - // wait for a response to see what happened - Byte[] buffer = new Byte[256]; - String page = String.Empty; - - // Poll for data until 30-second timeout. Returns true for data and connection closed. - while (thingSpeakSocket.Poll(20 * 1000000, SelectMode.SelectRead)) - { - // If there are 0 bytes in the buffer, then the connection is closed, or we have timed out. - if (thingSpeakSocket.Available == 0) break; - - // Zero all bytes in the re-usable buffer. - Array.Clear(buffer, 0, buffer.Length); - - // Read a buffer-sized HTML chunk. - Int32 bytesRead = thingSpeakSocket.Receive(buffer); - // Append the chunk to the string. - page = page + new String(Encoding.UTF8.GetChars(buffer)); - } - - Debug.Print(page); - } - } - } -} +using System; +using System.Collections; +using System.Net; +using System.Net.Sockets; +using System.Text; +using Controller; +using Microsoft.SPOT; +using System.Threading; + +namespace Plugins +{ + public class Thingspeak : OutputPlugin + { + ~Thingspeak() { Dispose(); } + public override void Dispose() { } + private string m_httpPost; + private DateTime NextUpdateTime; + string NextFieldData; + private const string m_thingSpeakIP = "api.thingspeak.com"; + + public Thingspeak() { } + + public Thingspeak(object _config) + { + Hashtable config = (Hashtable)_config; + m_httpPost = "POST /update HTTP/1.1\nHost: api.thingspeak.com\nConnection: close\nX-THINGSPEAKAPIKEY: "; + m_httpPost += config["writeapi"].ToString() + "\n"; + m_httpPost += "Content-Type: application/x-www-form-urlencoded\nContent-Length: "; + NextUpdateTime = DateTime.Now; + NextFieldData = ""; + } + + private static Socket ConnectSocket(String server, Int32 port) + { + // Get server's IP address. + IPHostEntry hostEntry = Dns.GetHostEntry(server); + + // Create socket and connect to the server's IP address and port + Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + socket.ReceiveTimeout = 5000; + socket.SendTimeout = 5000; + socket.Connect(new IPEndPoint(hostEntry.AddressList[0], port)); + return socket; + } + + public override void EventHandler(object _sender, IPluginData _data) + { + try + { + //Build ThingSpeak Post Data + string fieldData = ""; + foreach (PluginData _pd in _data.GetData()) + { + if (_pd.LastReadSuccess) + { + // build field string from _data and append to the post string + if (NextFieldData.Contains("field" + _pd.ThingSpeakFieldID.ToString())) + { + string[] _Fields = NextFieldData.Split('&'); + NextFieldData = ""; + foreach (string f in _Fields) + if (!(f.Contains("field" + _pd.ThingSpeakFieldID.ToString()))) + NextFieldData += f + "&"; + } + else + fieldData += "field" + _pd.ThingSpeakFieldID + "=" + _pd.Value.ToString("F") + "&"; + } + } + // add the last fielddata to the current and remove the last & from the string. + fieldData += NextFieldData; + NextFieldData = ""; + fieldData = fieldData.TrimEnd('&'); + + //Only post if there is some data. + if (!(fieldData == "") && NextUpdateTime <= DateTime.Now) + { + string postString = m_httpPost + fieldData.Length + "\n\n" + fieldData; + //Debug.Print("Post String: " + postString); + //Open the Socket and post the data + // create required networking parameters + + using (Socket thingSpeakSocket = ConnectSocket(m_thingSpeakIP, 80)) + { + Byte[] sendBytes = Encoding.UTF8.GetBytes(postString); + thingSpeakSocket.Send(sendBytes, sendBytes.Length, 0); + + // wait for a response to see what happened + Byte[] buffer = new Byte[256]; + String page = String.Empty; + + // Poll for data until 30-second timeout. Returns true for data and connection closed. + while (thingSpeakSocket.Poll(20 * 1000000, SelectMode.SelectRead)) + { + // If there are 0 bytes in the buffer, then the connection is closed, or we have timed out. + if (thingSpeakSocket.Available == 0) break; + + // Zero all bytes in the re-usable buffer. + Array.Clear(buffer, 0, buffer.Length); + + // Read a buffer-sized HTML chunk. + Int32 bytesRead = thingSpeakSocket.Receive(buffer); + // Append the chunk to the string. + page = page + new String(Encoding.UTF8.GetChars(buffer)); + } + thingSpeakSocket.Close(); + //Update the time that the next update can happen. + NextUpdateTime = DateTime.Now.AddSeconds(15); + //Debug.Print(fieldData + " " + "transmitted to ThingSpeak"); + //Debug.Print("Received: " + page.ToString()); + if (page.Contains("1\r\n0\r\n0")) + { + //Reschedule for future posting if it failed. + NextFieldData = fieldData; + throw new Exception("Failed to post data to ThingSpeak. Rescheduled post."); + } + } + } + else + { + //Store the fieldData for future post + NextFieldData = fieldData; + } + } + catch (Exception e) + { + Debug.Print(e.Message); + } + } + } +} diff --git a/Plugin-Thingspeak/thingspeak.js b/Plugin-Thingspeak/thingspeak.js index e2f658d..f64e839 100644 --- a/Plugin-Thingspeak/thingspeak.js +++ b/Plugin-Thingspeak/thingspeak.js @@ -1,4 +1,4 @@ -var thingspeakInit = function() { +var ThingspeakInit = function() { var pluginEnabled = (config.config.output.Thingspeak.enabled === 'true'); $("#API").val(config.config.output.Thingspeak.writeapi); $("#API").change(function() { config.config.output.Thingspeak.writeapi = $(this).val(); }); diff --git a/Plugin-pH/Plugin-pH.csproj b/Plugin-pH/Plugin-pH.csproj index dcd2581..bc5191b 100644 --- a/Plugin-pH/Plugin-pH.csproj +++ b/Plugin-pH/Plugin-pH.csproj @@ -1,61 +1,63 @@ - - - - pH - Library - Plugins - {b69e3092-b931-443c-abe7-7e7b65f2a37f};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 9.0.21022 - 2.0 - {428E9964-527F-48D8-B88A-8A22435F481B} - v4.2 - $(MSBuildExtensionsPath32)\Microsoft\.NET Micro Framework\ - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - OnOutputUpdated - - - - - - - - - - - - - - - - - - - - - {117848BD-213C-4AE4-8F58-D27F14DAA534} - Controller - - - - copy $(TargetDir)le\$(TargetName).pe g:\plugins -copy $(ProjectDir)$(TargetName).htm g:\plugins - + + + + pH + Library + Plugins + {b69e3092-b931-443c-abe7-7e7b65f2a37f};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 9.0.21022 + 2.0 + {428E9964-527F-48D8-B88A-8A22435F481B} + v4.2 + $(MSBuildExtensionsPath32)\Microsoft\.NET Micro Framework\ + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + OnOutputUpdated + + + + + + + + + + + + + + + + + + + + + {117848BD-213C-4AE4-8F58-D27F14DAA534} + Controller + + + + copy $(TargetDir)le\$(TargetName).pe g:\plugins\ + +copy $(ProjectDir)$(TargetName).htm g:\plugins\ +copy $(ProjectDir)$(TargetName).js g:\files\$(TargetName).min.js + \ No newline at end of file diff --git a/Plugin-pH/pH.cs b/Plugin-pH/pH.cs index f1400f9..91f1abe 100644 --- a/Plugin-pH/pH.cs +++ b/Plugin-pH/pH.cs @@ -1,138 +1,172 @@ -using Controller; -using System; -using Microsoft.SPOT; -using System.IO.Ports; -using System.Text; -using System.Threading; -using System.Collections; - -namespace Plugins -{ - - public class AlkalinityData : IPluginData - { - private float m_value; - public float GetValue() { return m_value; } - public void SetValue(float _value) { m_value = _value; } - public string DataUnits() { return "pH"; } - public ThingSpeakFields DataType() { return ThingSpeakFields.pH; } - } - - /// - /// pH input data using the pH kit from Atlast Scientific - /// http://atlas-scientific.com/product_pages/kits/ph-kit.html - /// The pH stamp provides simple UART based serial communication for data. - /// It also provides temperature compensation, so the pH plugin has both an - /// Input Callback and and Output Callback, to allow the temperature input - /// plugin to update the pH plugin. - /// - public class pH : InputPlugin - { - private TimeSpan m_timerInterval; - public override TimeSpan TimerInterval { get { return m_timerInterval; } } - - /// - /// Latest temperature reading passed in from the Temperature Plugin - /// - private float m_Temperature; - - public pH() { m_Temperature = 0.0F; } - public pH(object _config) : base() - { - Hashtable config = (Hashtable)_config; - // The Timer Interval is specified in an Arraylist of numbers - ArrayList interval = config["interval"] as ArrayList; - - //TODO: Double casting since for some reason an explicit cast from a Double - // to an Int doesn't work. It's a boxing issue, as interval[i] returns an object. - // And JSON.cs returns numbers as doubles - int[] times = new int[3]; - for (int i = 0; i < 3; i++) { times[i] = (int)(double)interval[i]; } - m_timerInterval = new TimeSpan(times[0], times[1], times[2]); - } - - ~pH() { Dispose(); } - public override void Dispose() { } - - /// - /// Records the last temperature reading from the Temperature Plugin - /// - /// Object that raised the callback - /// Last reading - public override void EventHandler(object _sender, IPluginData _data) - { - // Only worry about Temperature data, so check data units. - // If it's 'C' then assume it's the one we want. - Debug.Print("Got Temperature Value"); - if (_data.DataUnits().Equals("C")) m_Temperature = _data.GetValue(); - } - - public override void TimerCallback(object state) - { - Debug.Print("pH Callback"); - AlkalinityData phData = new AlkalinityData(); - - // get current pH Value - phData.SetValue(CalculatePH()); - Debug.Print("pH = " + phData.GetValue().ToString("F")); - - //Timer Callbacks receive a Delegate in the state object - InputDataAvailable ida = (InputDataAvailable)state; - - // call out to the delegate with expected value - // TODO: Currently there is a glitch with SerialPort and - // sometimes data doesn't come back from the Stamp. - // Discard bad readings, and report any meaningful ones - if (phData.GetValue() > 0.0F) ida(phData); - } - - /// - /// Takes reading from Atlas Scientific pH Stamp - /// - /// - private float CalculatePH() - { - float ph = 0.0F; - SerialPort sp = new SerialPort(Serial.COM1, 38400, Parity.None, 8, StopBits.One); - sp.ReadTimeout = 1000; - - try - { - string command = ""; - string response = ""; - char inChar; - - // Send the temperature reading if available - if (m_Temperature > 0) - command = m_Temperature.ToString("F") + "\rR\r"; - else - command = "R\r"; - - Debug.Print(command); - byte[] message = Encoding.UTF8.GetBytes(command); - - sp.Open(); - sp.Write(message, 0, message.Length); - sp.Flush(); - Debug.Print("sending message"); - - // Now collect response - while ((inChar = (char)sp.ReadByte()) != '\r') { response += inChar; } - - // Stamp can return text if reading was not successful, so test before returning - double phReading; - if (Double.TryParse(response, out phReading)) ph = (float)phReading; - } - catch (Exception e) - { - Debug.Print(e.StackTrace); - } - finally - { - sp.Close(); - sp.Dispose(); - } - return ph; - } - } +using Controller; +using System; +using Microsoft.SPOT; +using System.IO.Ports; +using System.Text; +using System.Threading; +using System.Collections; + +namespace Plugins +{ + + public class AlkalinityData : IPluginData + { + private PluginData[] _PluginData; + public PluginData[] GetData() { return _PluginData; } + public void SetData(PluginData[] _value) { _PluginData = _value; } + } + + /// + /// pH input data using the pH kit from Atlast Scientific + /// http://atlas-scientific.com/product_pages/kits/ph-kit.html + /// The pH stamp provides simple UART based serial communication for data. + /// It also provides temperature compensation, so the pH plugin has both an + /// Input Callback and and Output Callback, to allow the temperature input + /// plugin to update the pH plugin. + /// + public class pH : InputPlugin + { + private TimeSpan m_timerInterval; + public override TimeSpan TimerInterval { get { return m_timerInterval; } } + public override bool ImplimentsEventHandler() { return true; } + + /// + /// Latest temperature reading passed in from the Temperature Plugin + /// + private PluginData m_Temperature = new PluginData(); + + public pH() { } + public pH(object _config) : base() + { + Hashtable config = (Hashtable)_config; + // The Timer Interval is specified in an Arraylist of numbers + ArrayList interval = config["interval"] as ArrayList; + + //TODO: Double casting since for some reason an explicit cast from a Double + // to an Int doesn't work. It's a boxing issue, as interval[i] returns an object. + // And JSON.cs returns numbers as doubles + int[] times = new int[3]; + for (int i = 0; i < 3; i++) { times[i] = (int)(double)interval[i]; } + m_timerInterval = new TimeSpan(times[0], times[1], times[2]); + } + + ~pH() { Dispose(); } + public override void Dispose() { } + + /// + /// Records the last temperature reading from the Temperature Plugin + /// + /// Object that raised the callback + /// Last reading + public override void EventHandler(object _sender, IPluginData _data) + { + // Only worry about Temperature data, so check data units. + // If it's 'C' and the Name = 'Temperature' then assume it's the one we want. + foreach (PluginData _pd in _data.GetData()) + { + if (_pd.Name.Equals("Temperature") && _pd.UnitOFMeasurment.Equals("C") && _pd.LastReadSuccess) + { + Debug.Print("PH Plugin Got Temperature Value"); + m_Temperature = _pd; + } + } + } + + public override void TimerCallback(object state) + { + AlkalinityData phData = new AlkalinityData(); + + // get current pH Value + phData.SetData(CalculatePH()); + + foreach (PluginData pd in phData.GetData()) + { + Debug.Print(pd.Name + " = " + pd.Value.ToString("F")); + } + + //Timer Callbacks receive a Delegate in the state object + InputDataAvailable ida = (InputDataAvailable)state; + + // call out to the delegate with expected value + // TODO: Currently there is a glitch with SerialPort and + // sometimes data doesn't come back from the Stamp. + // Discard bad readings, and report any meaningful ones + //if ((float)phData.GetData()[0].Value > 0.0F) + ida(phData); + } + + /// + /// Takes reading from Atlas Scientific pH Stamp + /// + /// + private PluginData[] CalculatePH() + { + double ph = 0.0; + PluginData[] _PluginData = new PluginData[1]; + _PluginData[0] = new PluginData(); + SerialPort sp = new SerialPort(Serial.COM2, 38400, Parity.None, 8, StopBits.One); + sp.ReadTimeout = 6000; + sp.WriteTimeout = 4000; + bool ReadSuccess = true; + + try + { + string command = ""; + string response = ""; + char inChar; + + // Send the temperature reading if available + if (m_Temperature.LastReadSuccess) + command = "\r" + m_Temperature.Value.ToString("F") + "R\r"; + else + command = "\rR\r"; + + byte[] message = Encoding.UTF8.GetBytes(command); + + Debug.Print("sending message"); + sp.Open(); + sp.Write(message, 0, message.Length); + sp.Flush(); + Thread.Sleep(2000); + + // Now collect response + try + { + while (sp.BytesToRead > 0) + { + inChar = (char)sp.ReadByte(); + if (inChar != '\r' && inChar != '\0') + response += inChar; + } + Debug.Print("Response:" + response); + } + catch (Exception e) + { + Debug.Print("Could not read from the PH stamp. Please check the connection."); + ReadSuccess = false; + } + + // Stamp can return text if reading was not successful, so test before returning + double phReading; + if (Double.TryParse(response, out phReading)) ph = (float)phReading; + } + catch (Exception e) + { + Debug.Print(e.Message); + ReadSuccess = false; + } + finally + { + _PluginData[0].Name = "pH"; + _PluginData[0].UnitOFMeasurment = "pH"; + _PluginData[0].Value = ph; + _PluginData[0].ThingSpeakFieldID = 2; + _PluginData[0].LastReadSuccess = ReadSuccess; + + if (sp.IsOpen) sp.Close(); + sp.Dispose(); + } + return _PluginData; + } + } } \ No newline at end of file diff --git a/Plugin-pH/pH.min.js b/Plugin-pH/pH.min.js deleted file mode 100644 index dc20cdf..0000000 --- a/Plugin-pH/pH.min.js +++ /dev/null @@ -1 +0,0 @@ -var pHInit=function(){var pluginEnabled=(config.config.input.pH.enabled==='true');var phEnable=$('#PpHE');phEnable.attr('checked',pluginEnabled);phEnable.click(function(){$(this).button("option","label",this.checked?"Enabled":"Disabled");config.config.input.pH.enabled=this.checked?'true':'false';});phEnable.button({label:(pluginEnabled?"Enabled":"Disabled")}).button('refresh');}; \ No newline at end of file diff --git a/WebServer.NM/JSONLib/JSONLib.csproj b/WebServer.NM/JSONLib/JSONLib.csproj new file mode 100644 index 0000000..7fbcb47 --- /dev/null +++ b/WebServer.NM/JSONLib/JSONLib.csproj @@ -0,0 +1,43 @@ + + + + JSONLib + Library + JSONLib + {b69e3092-b931-443c-abe7-7e7b65f2a37f};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 9.0.21022 + 2.0 + {48B8AB1A-D086-4058-AB66-E3B10B9A8DC1} + v4.2 + $(MSBuildExtensionsPath32)\Microsoft\.NET Micro Framework\ + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebServer.NM/JSONLib/Json.cs b/WebServer.NM/JSONLib/Json.cs new file mode 100644 index 0000000..857bf04 --- /dev/null +++ b/WebServer.NM/JSONLib/Json.cs @@ -0,0 +1,123 @@ +using System; +using System.Reflection; + +namespace FastloadMedia.NETMF.Http +{ + /// + /// This static class contains a method to convert a value to its JSON equivalent. + /// Programmed by Huysentruit Wouter + /// + public static class Json + { + /// + /// Lookup table for hex values. + /// + private const string HEX_CHARS = "0123456789ABCDEF"; + public const string ContentType = "application/json"; + + /// + /// Converts a character to its javascript unicode equivalent. + /// + /// The character to convert. + /// The javascript unicode equivalent. + private static string JsUnicode(char c) + { + string result = "\\u"; + ushort value = (ushort)c; + + for (int i = 0; i < 4; i++, value <<= 4) + result += HEX_CHARS[value >> 12]; + + return result; + } + + /// + /// Encodes a javascript string. + /// + /// The string to encode. + /// The encoded string. + private static string JsEncode(string s) + { + string result = ""; + + foreach (char c in s) + { + if (c < (char)127) + { + switch (c) + { + case '\b': result += "\\b"; break; + case '\t': result += "\\t"; break; + case '\n': result += "\\n"; break; + case '\f': result += "\\f"; break; + case '\r': result += "\\r"; break; + case '"': result += "\\\""; break; + case '/': result += "\\/"; break; + case '\\': result += "\\\\"; break; + default: + if (c < ' ') + result += JsUnicode(c); + else + result += c; + break; + } + } + else + result += JsUnicode(c); + } + + return result; + } + + /// + /// Convert a value to JSON. + /// + /// The value to convert. Supported types are: Boolean, String, Byte, (U)Int16, (U)Int32, Float, Double, Decimal, JsonObject, JsonArray, Array, Object and null. + /// The JSON object as a string or null when the value type is not supported. + /// For objects, only public fields are converted. + public static string ToJson(object o) + { + if (o == null) + return "null"; + + Type type = o.GetType(); + switch (type.Name) + { + case "Boolean": + return (bool)o ? "true" : "false"; + case "String": + return "\"" + JsEncode((string)o) + "\""; + case "Byte": + case "Int16": + case "UInt16": + case "Int32": + case "UInt32": + case "Single": + case "Double": + case "Decimal": + case "JsonObject": + case "JsonArray": + return o.ToString(); + } + + if (type.IsArray) + { + JsonArray jsonArray = new JsonArray(); + foreach (object i in (Array)o) + jsonArray.Add(i); + return jsonArray.ToString(); + } + + if (type.IsClass) + { + JsonObject jsonObject = new JsonObject(); + FieldInfo[] fields = type.GetFields(); + foreach (FieldInfo info in fields) + jsonObject.Add(info.Name, info.GetValue(o)); + return jsonObject.ToString(); + } + + return null; + } + } +} diff --git a/WebServer.NM/JSONLib/JsonArray.cs b/WebServer.NM/JSONLib/JsonArray.cs new file mode 100644 index 0000000..3a9fcbf --- /dev/null +++ b/WebServer.NM/JSONLib/JsonArray.cs @@ -0,0 +1,38 @@ +using System.Collections; + +namespace FastloadMedia.NETMF.Http +{ + /// + /// A Json Array. + /// Programmed by Huysentruit Wouter + /// + /// Supported value types are those supported by the Json.ToJson method. + /// See the Json.ToJson method for more information. + /// + public class JsonArray : ArrayList + { + /// + /// Convert the array to its JSON representation. + /// + /// A string containing the JSON representation of the array. + public override string ToString() + { + string[] parts = new string[Count]; + + for (int i = 0; i < Count; i++) + parts[i] = Json.ToJson(this[i]); + + string result = ""; + + foreach (string part in parts) + { + if (result.Length > 0) + result += ", "; + + result += part; + } + + return "[" + result + "]"; + } + } +} diff --git a/WebServer.NM/JSONLib/JsonObject.cs b/WebServer.NM/JSONLib/JsonObject.cs new file mode 100644 index 0000000..748fdc8 --- /dev/null +++ b/WebServer.NM/JSONLib/JsonObject.cs @@ -0,0 +1,46 @@ +using System.Collections; + +namespace FastloadMedia.NETMF.Http +{ + /// + /// A Json Object. + /// Programmed by Huysentruit Wouter + /// + /// Keys must be strings! + /// Supported value types are those supported by the Json.ToJson method. + /// See the Json.ToJson method for more information. + /// + public class JsonObject : Hashtable + { + /// + /// Convert the object to its JSON representation. + /// + /// A string containing the JSON representation of the object. + public override string ToString() + { + string result = ""; + + string[] keys = new string[Count]; + object[] values = new object[Count]; + + Keys.CopyTo(keys, 0); + Values.CopyTo(values, 0); + + for (int i = 0; i < Count; i++) + { + if (result.Length > 0) + result += ", "; + + string value = Json.ToJson(values[i]); + if (value == null) + continue; + + result += "\"" + keys[i] + "\""; + result += ": "; + result += value; + } + + return "{" + result + "}"; + } + } +} diff --git a/WebServer.NM/JSONLib/Properties/AssemblyInfo.cs b/WebServer.NM/JSONLib/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..381be93 --- /dev/null +++ b/WebServer.NM/JSONLib/Properties/AssemblyInfo.cs @@ -0,0 +1,25 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("JSONLib")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("JSONLib")] +[assembly: AssemblyCopyright("Copyright © 2011")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/WebServer.NM/NeonMika.NETMF.Webserver/NeonMika.Webserver.csproj b/WebServer.NM/NeonMika.NETMF.Webserver/NeonMika.Webserver.csproj new file mode 100644 index 0000000..1ebe754 --- /dev/null +++ b/WebServer.NM/NeonMika.NETMF.Webserver/NeonMika.Webserver.csproj @@ -0,0 +1,89 @@ + + + + NeonMika.Webserver + Library + NeonMika.Webserver + {b69e3092-b931-443c-abe7-7e7b65f2a37f};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 9.0.21022 + 2.0 + {7F9FBC4B-0DBD-499F-AB06-FD5C1ED26A67} + v4.2 + $(MSBuildExtensionsPath32)\Microsoft\.NET Micro Framework\ + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + True + True + Resources.resx + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + {48B8AB1A-D086-4058-AB66-E3B10B9A8DC1} + JSONLib + + + {A0B9E75A-9052-4140-A86A-9AC82B484F98} + NeonMika.Util + + + {0AD21E9F-0370-40B6-B045-109A266C9FC2} + NeonMika.XML + + + \ No newline at end of file diff --git a/WebServer.NM/NeonMika.NETMF.Webserver/POST/PostFileReader.cs b/WebServer.NM/NeonMika.NETMF.Webserver/POST/PostFileReader.cs new file mode 100644 index 0000000..e7f4543 --- /dev/null +++ b/WebServer.NM/NeonMika.NETMF.Webserver/POST/PostFileReader.cs @@ -0,0 +1,40 @@ +using System; +using Microsoft.SPOT; +using System.IO; + +namespace NeonMika.Webserver.POST +{ + public class PostFileReader : IDisposable + { + FileStream fs; + + public PostFileReader() + { + fs = new FileStream(Settings.POST_TEMP_PATH, FileMode.Open); + } + + public byte[] Read(int count) + { + byte[] arr = new byte[count]; + fs.Read(arr, 0, count); + return arr; + } + + public long Length { get { return fs.Length; } } + + public void Close() + { + try { fs.Close(); } + catch (Exception ex) { } + } + + #region IDisposable Members + + public void Dispose() + { + Close(); + } + + #endregion + } +} diff --git a/WebServer.NM/NeonMika.NETMF.Webserver/POST/PostToSdWriter.cs b/WebServer.NM/NeonMika.NETMF.Webserver/POST/PostToSdWriter.cs new file mode 100644 index 0000000..494bb2e --- /dev/null +++ b/WebServer.NM/NeonMika.NETMF.Webserver/POST/PostToSdWriter.cs @@ -0,0 +1,88 @@ +using System; +using Microsoft.SPOT; +using System.IO; +using System.Threading; +using System.Net.Sockets; +using System.Text; +using NeonMika.Util; +using NeonMika.Webserver.Responses; + +namespace NeonMika.Webserver.POST +{ + /// + /// Saves a POST-request at Setting.POST_TEMP_PATH + /// Sends back "OK" on success + /// + class PostToSdWriter : IDisposable + { + private byte[] _buffer; + private int _startAt; + private Request _e; + + public PostToSdWriter(Request e, byte[] buffer, int startAt) + { + _buffer=buffer; + _startAt = startAt; + _e = e; + } + + /// + /// Saves content to Setting.POST_TEMP_PATH + /// + /// The request which should be handeld + /// True if 200_OK was sent, otherwise false + public bool Receive() + { + Debug.Print(Debug.GC(true).ToString()); + Debug.Print(Debug.GC(true).ToString()); + + int availableBytes = Convert.ToInt32(_e.Headers["Content-Length"].ToString().TrimEnd('\r')); + + try + { + FileStream fs = new FileStream(Settings.POST_TEMP_PATH, FileMode.Create, FileAccess.Write); + Debug.Print(Debug.GC(true).ToString()); + Debug.Print(Debug.GC(true).ToString()); + + fs.Write(_buffer,_startAt,_buffer.Length-_startAt); + availableBytes -= _buffer.Length; + + //_buffer = new byte[availableBytes > Settings.MAX_REQUESTSIZE ? Settings.MAX_REQUESTSIZE : availableBytes]; + + //while (availableBytes > 0) + //{ + //if(availableBytes > Settings.MAX_REQUESTSIZE) + // _buffer = new byte[availableBytes]; + + //while (_e.Client.Available < _buffer.Length) + //{ + // Thread.Sleep(1); + //} + + //_e.Client.Receive(_buffer, _buffer.Length, SocketFlags.None); + //fs.Write(_buffer, 0, _buffer.Length); + //availableBytes -= Settings.MAX_REQUESTSIZE; + //} + + fs.Flush(); + fs.Close(); + } + catch (Exception ex) + { + Debug.Print("Error writing POST-data"); + return false; + } + + return true; + } + + #region IDisposable Members + + public void Dispose() + { + _buffer = new byte[0]; + } + + #endregion + } +} diff --git a/WebServer.NM/NeonMika.NETMF.Webserver/Properties/AssemblyInfo.cs b/WebServer.NM/NeonMika.NETMF.Webserver/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..74f7ea0 --- /dev/null +++ b/WebServer.NM/NeonMika.NETMF.Webserver/Properties/AssemblyInfo.cs @@ -0,0 +1,25 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("NeonMika.Webserver")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("NeonMika.Webserver")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2011")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/WebServer.NM/NeonMika.NETMF.Webserver/Properties/Resources.Designer.cs b/WebServer.NM/NeonMika.NETMF.Webserver/Properties/Resources.Designer.cs new file mode 100644 index 0000000..4b9787e --- /dev/null +++ b/WebServer.NM/NeonMika.NETMF.Webserver/Properties/Resources.Designer.cs @@ -0,0 +1,38 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.17929 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace NeonMika.Webserver.Properties +{ + + internal partial class Resources + { + private static System.Resources.ResourceManager manager; + internal static System.Resources.ResourceManager ResourceManager + { + get + { + if ((Resources.manager == null)) + { + Resources.manager = new System.Resources.ResourceManager("NeonMika.Webserver.Properties.Resources", typeof(Resources).Assembly); + } + return Resources.manager; + } + } + internal static string GetString(Resources.StringResources id) + { + return ((string)(Microsoft.SPOT.ResourceUtility.GetObject(ResourceManager, id))); + } + [System.SerializableAttribute()] + internal enum StringResources : short + { + indexHTML = 4256, + } + } +} diff --git a/WebServer.NM/NeonMika.NETMF.Webserver/Properties/Resources.resx b/WebServer.NM/NeonMika.NETMF.Webserver/Properties/Resources.resx new file mode 100644 index 0000000..541bb90 --- /dev/null +++ b/WebServer.NM/NeonMika.NETMF.Webserver/Properties/Resources.resx @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <h1>NeonMika.Webserver</h1> +<h3>Standard response actions:</h3> +<h4>XML:</h4> +<h6>echo</h6> +- [ipnetduino]/echo?value=[a-Z] +<h6>switchDigitalPin</h6> +- [ipnetduino]/switchDigitalPin?pin=[0-13] +<h6>setDigitalPin</h6> +- [ipnetduino]/setDigitalPin?pin=[0-13]&state=[true|false] +<h6>pwm</h6> +- [ipnetduino]/pwm?pin=[5|6|9|10]&period=[int]&duration=[int] +<h6>xmlResponselist</h6> +- [ipnetduino]/xmlResponselist +<h6>getAnalogPinValue</h6> +- [ipnetduino]/getAnalogPinValue?pin=[0-5] +<h6>getDigitalPinState</h6> +- [ipnetduino]/getDigitalPinState?pin=[0-13] +<h6>MultipleXML</h6> +- [ipnetduino]/multixml + +<h4>JSON:</h4> +<h6>jsonResponselist</h6> +- [ipnetduino]/jsonResponselist + +<h4>Complex:</h4> +<h6>IndexPage</h6> +- [ipnetduino]/ + +<h4>FileResponse:</h4> +- [ipnetduino]/\SD\[filepath] + + \ No newline at end of file diff --git a/WebServer.NM/NeonMika.NETMF.Webserver/Request.cs b/WebServer.NM/NeonMika.NETMF.Webserver/Request.cs new file mode 100644 index 0000000..d7f1ce5 --- /dev/null +++ b/WebServer.NM/NeonMika.NETMF.Webserver/Request.cs @@ -0,0 +1,157 @@ +using System; +using System.Text; +using System.Net.Sockets; +using System.Net; +using System.Diagnostics; +using System.IO; +using System.Collections; + +namespace NeonMika.Webserver +{ + /// + /// Contains information about a request + /// + public class Request : IDisposable + { + /// + /// Socket that sent the request + /// + public Socket Client { get; private set; } + + protected string _method; + protected string _url; + protected Hashtable _getArguments = new Hashtable(); + protected Hashtable _headers = new Hashtable(); + + /// + /// All header lines + /// + public Hashtable Headers + { + get { return _headers; } + set { _headers = value; } + } + + /// + /// Hashtable with all GET key-value pa in it + /// + public Hashtable GetArguments + { + get { return _getArguments; } + private set { _getArguments = value; } + } + + /// + /// HTTP verb (Request method) + /// + public string Method + { + get { return _method; } + private set { _method = value; } + } + + /// + /// URL of request without GET values + /// + public string URL + { + get { return _url; } + private set { + if (value == "") + _url = Settings.DEFAULT_PAGE; + else + _url = value; + } + } + + /// + /// Creates request + /// + /// Input from network + /// Socket that sent the request + public Request(char[] header, Socket client) + { + this.Client = client; + ProcessHeader(header); + } + + /// + /// Fills the Request with the header values + /// + /// Input from network + private void ProcessHeader(char[] data) + { + bool replace = false; + + for (int i = 0; i < data.Length-3; i++) + { + replace = false; + + switch (data[i].ToString()+data[i+1]+data[i+2]) + { + case "%5C": + data[i] = '\\'; + data[i+1] = '\0'; + data[i+2] = '\0'; + replace = true; + break; + + case "%2F": + data[i] = '/'; + data[i+1] = '\0'; + data[i+2] = '\0'; + replace = true; + break; + } + + if(replace) + for (int x = i + 3; x < data.Length; x++) + if (data[x] != '\0') + { + data[x - 2] = data[x]; + data[x] = '\0'; + } + } + + string content = new string(data); + string[] lines = content.Split('\n'); + + // Parse the first line of the request: "GET /path/ HTTP/1.1" + string[] firstLineSplit = lines[0].Split(' '); + _method = firstLineSplit[0]; + string[] path = firstLineSplit[1].Split('?'); + this.URL = path[0].Substring(1); // Substring to ignore the leading '/' + + _getArguments.Clear(); + if (path.Length > 1) + ProcessGETParameters(path[1]); + + Headers = NeonMika.Util.Converter.ToHashtable(lines, ": ", 1); + } + + /// + /// Generated Key-Value-Hashtable for GET-Parameters + /// + /// + private void ProcessGETParameters(string parameters) + { + _getArguments = new Hashtable(); + string[] urlArguments = parameters.Split('&'); + + _getArguments = NeonMika.Util.Converter.ToHashtable(urlArguments, "="); + } + + #region IDisposable Members + + public void Dispose() + { + if(_headers != null) + _headers.Clear(); + + if(_getArguments != null) + _getArguments.Clear(); + } + + #endregion + } +} \ No newline at end of file diff --git a/WebServer.NM/NeonMika.NETMF.Webserver/Responses/ComplexResponses/IndexResponse.cs b/WebServer.NM/NeonMika.NETMF.Webserver/Responses/ComplexResponses/IndexResponse.cs new file mode 100644 index 0000000..0c5c062 --- /dev/null +++ b/WebServer.NM/NeonMika.NETMF.Webserver/Responses/ComplexResponses/IndexResponse.cs @@ -0,0 +1,50 @@ +using System; +using Microsoft.SPOT; +using NeonMika.Webserver.Responses; +using System.Text; +using System.IO; +using System.Reflection; + +namespace NeonMika.Webserver.Responses.ComplexResponses +{ + /// + /// If the root directory is requested, this infopage will be shown + /// + public class IndexResponse : Response + { + /// + /// Page on which indexPage should be displayed + /// + public IndexResponse(string indexPage) + : base(indexPage) + { } + + /// + /// Execute this to check if SendResponse shoul be executed + /// + /// The request that should be handled + /// True if URL refers to this method, otherwise false (false = SendRequest should not be exicuted) + public override bool ConditionsCheckAndDataFill(Request e) + { + if (e.URL == "") + return true; + else + return false; + } + + /// + /// Sends infotext to client + /// + /// The request which should be handled + /// True if 200_OK was sent, otherwise false + public override bool SendResponse(Request e) + { + Debug.Print(Debug.GC(true).ToString()); + string index = Properties.Resources.GetString(Properties.Resources.StringResources.indexHTML); + Send200_OK("text/html", index.Length, e.Client); + SendData(e.Client,Encoding.UTF8.GetBytes(index)); + index = null; + return true; + } + } +} diff --git a/WebServer.NM/NeonMika.NETMF.Webserver/Responses/FileResponse.cs b/WebServer.NM/NeonMika.NETMF.Webserver/Responses/FileResponse.cs new file mode 100644 index 0000000..83e4187 --- /dev/null +++ b/WebServer.NM/NeonMika.NETMF.Webserver/Responses/FileResponse.cs @@ -0,0 +1,171 @@ +using System; +using System.Text; +using System.IO; +using System.Net.Sockets; +using Microsoft.SPOT; +using System.Collections; +using Microsoft.SPOT.Net.NetworkInformation; + +namespace NeonMika.Webserver.Responses +{ + /// + /// Standard response sending file to client + /// If filename is a directory, a directory-overview will be displayed + /// + public class FileResponse : Response + { + public FileResponse(String name = "FileResponse") + : base(name) + { } + + /// + /// File response has no conditions + /// + /// + /// + public override bool ConditionsCheckAndDataFill(Request e) + { + return true; + } + + /// + /// Depending on the requested path, a file, a directory-overview or a 404-error will be returned + /// + /// + /// + public override bool SendResponse(Request e) + { + string filePath = NeonMika.Webserver.Settings.ROOT_SD_PATH + e.URL; + bool isDirectory = false; + + char[] chars = filePath.ToCharArray(); + filePath=""; + + for (int i = 0; i < chars.Length; i++) + if (chars[i] == '/') + filePath+="\\"; + else + filePath+=chars[i]; + + chars = new char[0]; + + //File found check + if ( !CheckFileDirectoryExist(ref filePath, out isDirectory) ) + Send404_NotFound(e.Client); + + if (isDirectory) + { + ArrayList toReturn = new ArrayList(); + string send; + var interf = NetworkInterface.GetAllNetworkInterfaces()[0]; + + Send200_OK(MimeType(".html"), 0, e.Client); + + string uppath = ((filePath.LastIndexOf("\\") >= 0) ? interf.IPAddress + ((filePath[0] != '\\') ? "\\" : "") + filePath.Substring(0, filePath.LastIndexOf("\\")) : interf.IPAddress + ((filePath[0] != '\\') ? "\\" : "") + filePath); + + send = "" + e.URL + "" + + "" + + "One level up
" + + "

" + e.URL + "

Directories:

"; + if (SendData(e.Client, Encoding.UTF8.GetBytes(send)) == 0) + return false; + + foreach (string d in Directory.GetDirectories(filePath)) + { + send = "" + d + "
"; + if (SendData(e.Client, Encoding.UTF8.GetBytes(send)) == 0) + return false; + } + + SendData(e.Client, Encoding.UTF8.GetBytes("

Files:

")); + + foreach (string f in Directory.GetFiles(filePath)) + { + send = "" + f + "
"; + if (SendData(e.Client, Encoding.UTF8.GetBytes(send)) == 0) + return false; + } + + send = ""; + if (SendData(e.Client, Encoding.UTF8.GetBytes(send)) == 0) + return false; + } + else + { + string mType = MimeType(filePath); + + //File sending + using (FileStream inputStream = new FileStream(filePath, FileMode.Open)) + { + Send200_OK(mType, (int)inputStream.Length, e.Client); + + byte[] readBuffer = new byte[Settings.FILE_BUFFERSIZE]; + int sentBytes = 0; + + //Sending parts in size of "Settings.FILE_BUFFERSIZE" + while (sentBytes < inputStream.Length) + { + int bytesRead = inputStream.Read(readBuffer, 0, readBuffer.Length); + try + { + if (SocketConnected(e.Client)) + { + sentBytes += e.Client.Send(readBuffer, bytesRead, SocketFlags.None); + } + else + { + e.Client.Close(); + return false; + } + } + catch (Exception ex) + { + Debug.Print("Error at sending bytes"); + try + { + e.Client.Close(); + } + catch (Exception ex2) + { + Debug.Print("Error at closing socket"); + } + + return false; + } + } + } + } + + return true; + } + + private static bool CheckFileDirectoryExist(ref string filePath, out bool isDirectory) + { + isDirectory = false; + //File found check + try + { + if ( filePath == "" ) + return false; + + if ( !File.Exists(filePath) ) + if ( !Directory.Exists(filePath) ) + { + isDirectory = false; + return false; + } + else + isDirectory = true; + return true; + } + catch ( Exception ex ) + { + Debug.Print("Error accessing file/directory"); + Debug.Print(ex.ToString( )); + return false; + } + } + } +} + + \ No newline at end of file diff --git a/WebServer.NM/NeonMika.NETMF.Webserver/Responses/JSONResponse.cs b/WebServer.NM/NeonMika.NETMF.Webserver/Responses/JSONResponse.cs new file mode 100644 index 0000000..001072e --- /dev/null +++ b/WebServer.NM/NeonMika.NETMF.Webserver/Responses/JSONResponse.cs @@ -0,0 +1,69 @@ +using System; +using System.Text; +using System.Net.Sockets; +using Microsoft.SPOT; +using FastloadMedia.NETMF.Http; + +namespace NeonMika.Webserver.Responses +{ + public class JSONResponse : Response + { + /// + /// This class knows, HOW to send back the data to the client + /// Write your own JSONResponseMethod, create a JSONResponse with JSONResponse(url,JSONResponseMethod), and add this response to your webserver instance + /// + public JSONResponse(string url, JSONResponseMethod method) + : base(url) + { + this._ResponseMethod = method; + _JSONArray = new JsonArray(); + } + + private JSONResponseMethod _ResponseMethod; + private JsonArray _JSONArray; + + /// + /// Execute this to check if SendResponse should be executed + /// + /// The request that should be handled + /// True if URL refers to this method, otherwise false (false = SendRequest should not be executed) + public override bool ConditionsCheckAndDataFill(Request e) + { + _JSONArray.Clear(); + if (e.URL == this.URL) + _ResponseMethod(e, _JSONArray); + else + return false; + return true; + } + + /// + /// Sends JSON to client + /// + /// Request that should be handled + /// True if 200_OK was sent, otherwise false + public override bool SendResponse(Request e) + { + String jsonResponse = String.Empty; + + jsonResponse = _JSONArray.ToString(); + + byte[] bytes = Encoding.UTF8.GetBytes(jsonResponse); + + int byteCount = bytes.Length; + + try + { + Send200_OK("application/json", byteCount, e.Client); + SendData(e.Client, bytes); + } + catch (Exception ex) + { + Debug.Print(ex.ToString()); + return false; + } + + return true; + } + } +} diff --git a/WebServer.NM/NeonMika.NETMF.Webserver/Responses/Response.cs b/WebServer.NM/NeonMika.NETMF.Webserver/Responses/Response.cs new file mode 100644 index 0000000..1034e3d --- /dev/null +++ b/WebServer.NM/NeonMika.NETMF.Webserver/Responses/Response.cs @@ -0,0 +1,201 @@ +using System; +using System.Text; +using System.Net.Sockets; +using Microsoft.SPOT; + +namespace NeonMika.Webserver.Responses +{ + /// + /// Abstract class for responses + /// Contains basic operations for sending data to the client + /// + abstract public class Response : IDisposable + { + private string _url; + + public string URL + { + get { return _url; } + set { _url = value; } + } + + /// + /// Creates response to send back to client + /// + /// Webserver expand methods + public Response(string Name) + { + this._url = Name; + } + + /// + /// Creates header for 200 OK response + /// + /// MIME type of response + /// Byte count of response body + /// The Socket connected with the client + protected void Send200_OK(string MimeType, int ContentLength, Socket Client) + { + /* + StringBuilder headerBuilder = new StringBuilder(); + headerBuilder.Append("HTTP/1.0 200 OK\r\n"); + headerBuilder.Append("Content-Type: "); + headerBuilder.Append(MimeType); + headerBuilder.Append("; charset=utf-8\r\n"); + headerBuilder.Append("Content-Length: "); + headerBuilder.Append(ContentLength.ToString()); + headerBuilder.Append("\r\n"); + headerBuilder.Append("Connection: close\r\n\r\n"); + * */ + + String header; + if(ContentLength>0) + header = "HTTP/1.0 200 OK\r\n" + "Content-Type: " + MimeType + "; charset=utf-8\r\n" + "Content-Length: " + ContentLength.ToString() + "\r\n" + "Connection: close\r\n\r\n"; + else + header = "HTTP/1.0 200 OK\r\n" + "Content-Type: " + MimeType + "; charset=utf-8\r\n" + "Connection: close\r\n\r\n"; + + try + { + Client.Send(Encoding.UTF8.GetBytes(header), header.Length, SocketFlags.None); + } + catch (Exception e) + { + Debug.Print(e.Message.ToString()); + return; + } + } + + /// + /// Sends a 404 Not Found response + /// + protected void Send404_NotFound(Socket Client) + { + string header = "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\nConnection: close\r\n\r\nNeonMika.Webserver is sorry

NeonMika.Webserver is sorry!

Page not found

"; + if (Client != null) + Client.Send(Encoding.UTF8.GetBytes(header), header.Length, SocketFlags.None); + Debug.Print("Sent 404 Not Found"); + } + + /// + /// Sends data to the client + /// + /// Socket connected with the client + /// Byte-array to be transmitted + /// Bytes that were sent + protected int SendData(Socket client, byte[] data) + { + int ret = 0; + try + { + if (SocketConnected(client)) + ret = client.Send(data, data.Length, SocketFlags.None); + else + { + client.Close(); + } + } + catch (Exception ex) + { + Debug.Print("Error on sending data to client / Closing Client"); + try + { + client.Close(); + } + catch (Exception ex2) + { + } + } + + return ret; + } + + /// + /// Converts fileending into mime-type + /// + /// File name or complete file path + /// MIME type + protected string MimeType(string Filename) + { + string result = "text/html"; + int dot = Filename.LastIndexOf('.'); + + string ext = (dot >= 0) ? Filename.Substring(dot + 1) : ""; + switch (ext) + { + case "txt": + result = "text/plain"; + break; + case "htm": + case "html": + result = "text/html"; + break; + case "js": + result = "text/javascript"; + break; + case "css": + result = "text/css"; + break; + case "xml": + case "xsl": + result = "text/xml"; + break; + case "jpg": + case "jpeg": + result = "image/jpeg"; + break; + case "gif": + result = "image/gif"; + break; + case "png": + result = "image/png"; + break; + case "ico": + result = "x-icon"; + break; + case "mid": + result = "audio/mid"; + break; + default: + result = "application/octet-stream"; + break; + } + return result; + } + + /// + /// Checks if socket is still connected + /// + /// Socket that should be checked + /// True on still connect + protected bool SocketConnected(Socket s) + { + bool part1 = s.Poll(1000, SelectMode.SelectRead); + bool part2 = (s.Available == 0); + if (part1 & part2) + return false; + else + return true; + } + + /// + /// Override this method to implement a response logic. + /// + /// True if Response was sent, false if not + abstract public bool SendResponse(Request e); + + /// + /// Override this, check the URL and process data if needed + /// + /// True if SendResponse should be sent, false if not + abstract public bool ConditionsCheckAndDataFill(Request e); + + #region IDisposable Members + + public void Dispose() + { + + } + + #endregion + } +} diff --git a/WebServer.NM/NeonMika.NETMF.Webserver/Responses/ResponseErrorType.cs b/WebServer.NM/NeonMika.NETMF.Webserver/Responses/ResponseErrorType.cs new file mode 100644 index 0000000..bcc8c37 --- /dev/null +++ b/WebServer.NM/NeonMika.NETMF.Webserver/Responses/ResponseErrorType.cs @@ -0,0 +1,12 @@ + +namespace NeonMika.Webserver.Responses +{ + public enum ResponseErrorType + { + ParameterMissing, + ParameterConvertError, + ParameterRangeException, + InternalValueNotSet, + InternalOperationError + } +} \ No newline at end of file diff --git a/WebServer.NM/NeonMika.NETMF.Webserver/Responses/XMLResponse.cs b/WebServer.NM/NeonMika.NETMF.Webserver/Responses/XMLResponse.cs new file mode 100644 index 0000000..0a7a918 --- /dev/null +++ b/WebServer.NM/NeonMika.NETMF.Webserver/Responses/XMLResponse.cs @@ -0,0 +1,110 @@ +using System; +using System.Text; +using System.Net.Sockets; +using System.Collections; +using Microsoft.SPOT; +using NeonMika.XML; + +namespace NeonMika.Webserver.Responses +{ + /// + /// This class knows, HOW to send back the data to the client + /// Write your own XMLResponseMethod, create a XMLResponse with XMLResponse(url,XMlResponseMethod), and add this response to your webserver instance + /// + public class XMLResponse : Response + { + public XMLResponse(string url, XMLResponseMethod method) + : base(url) + { + this._ResponseMethod = method; + _Pairs = new Hashtable(); + } + + private XMLResponseMethod _ResponseMethod; + private Hashtable _Pairs; + + /// + /// Unifies the possible error messages + /// + /// Name of the parameter on which the error took place + /// Error type + /// Hashtable that has to be returned to the client + public static Hashtable GenerateErrorHashtable(String parameter, ResponseErrorType ret) + { + Hashtable h = new Hashtable(); + + switch (ret) + { + case ResponseErrorType.ParameterConvertError: + h.Add("error","Following parameter could not be converted: " + parameter + " to the right format (int, string, ...)."); + break; + case ResponseErrorType.ParameterMissing: + h.Add("error","Following parameter was not submitted: " + parameter + ". Please include it in your URL"); + break; + case ResponseErrorType.InternalValueNotSet: + h.Add("error","An internal error accured. Following value could not be set: " + parameter + ". Please check the requested method's source code"); + break; + case ResponseErrorType.ParameterRangeException: + h.Add("error","Following parameter was out of range: " + parameter + "."); + break; + case ResponseErrorType.InternalOperationError: + h.Add("error", "An internal error accured. Following code part threw the error: " + parameter + ". Please check the requested method's source code"); + break; + } + + return h; + } + + /// + /// Execute this to check if SendResponse shoul be executed + /// + /// The request that should be handled + /// True if URL refers to this method, otherwise false (false = SendRequest should not be exicuted) + public override bool ConditionsCheckAndDataFill(Request e) + { + _Pairs.Clear(); + if (e.URL == this.URL) + _ResponseMethod(e, _Pairs); + else + return false; + return true; + } + + /// + /// Sends XML to client + /// + /// The request which should be handled + /// True if 200_OK was sent, otherwise false + public override bool SendResponse(Request e) + { + String xml = ""; + xml += ""; + + xml += ""; + + foreach ( object h in _Pairs.Keys ) + xml += "<" + h + ">" + _Pairs[h].ToString() + ""; + + xml += ""; + + byte[] bytes = Encoding.UTF8.GetBytes(xml); + + int byteCount = bytes.Length; + + try + { + Send200_OK("text/xml", byteCount, e.Client); + SendData(e.Client, bytes); + } + catch (Exception ex) + { + Debug.Print(ex.ToString()); + return false; + } + + return true; + } + + + } +} diff --git a/WebServer.NM/NeonMika.NETMF.Webserver/Server.cs b/WebServer.NM/NeonMika.NETMF.Webserver/Server.cs new file mode 100644 index 0000000..f440e11 --- /dev/null +++ b/WebServer.NM/NeonMika.NETMF.Webserver/Server.cs @@ -0,0 +1,547 @@ +using System; +using System.Collections; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using FastloadMedia.NETMF.Http; +using Microsoft.SPOT; +using Microsoft.SPOT.Hardware; +using Microsoft.SPOT.Net.NetworkInformation; +using NeonMika.Webserver.Responses; +using NeonMika.XML; +using NeonMika.Util; +using NeonMika.Webserver.Responses.ComplexResponses; +using NeonMika.Webserver.POST; +using System.IO; + +namespace NeonMika.Webserver +{ + /// + /// XML Expansion methods have to be in this form + /// + /// Access to GET or POST arguments,... + /// This hashtable gets converted into xml on response + public delegate void XMLResponseMethod(Request e, Hashtable results); + + /// + /// JSON Expansion methods have to be in this form + /// + /// Access to GET or POST arguments,... + /// This JsonArray gets converted into JSON on response + /// True if URL refers to this method, otherwise false (false = SendRequest should not be executed) + public delegate void JSONResponseMethod(Request e, JsonArray results); + + public delegate void POSTOperationMethod(Request e, PostFileReader access); + + /// + /// Main class of NeonMika.Webserver + /// + public class Server + { + public int _PortNumber { get; private set; } + private Socket _ListeningSocket = null; + private Hashtable _Responses = new Hashtable( ); + + /// + /// Creates an NeonMika.Webserver instance running in a seperate thread + /// + /// The port to listen for incoming requests + public Server(int portNumber = 80) + { + var interf = NetworkInterface.GetAllNetworkInterfaces( )[0]; + + Debug.Print("Webserver is running on " + interf.IPAddress + " /// DHCP: " + interf.IsDhcpEnabled); + + this._PortNumber = portNumber; + + ResponseListInitialize( ); + + _ListeningSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + _ListeningSocket.Bind(new IPEndPoint(IPAddress.Any, portNumber)); + _ListeningSocket.Listen(4); + + var webserverThread = new Thread(WaitingForRequest); + webserverThread.Start( ); + } + + /// + /// Waiting for client to connect. + /// When bytes were read they get wrapped to a "Reqeust" + /// + private void WaitingForRequest( ) + { + while ( true ) + { + try + { + using ( Socket clientSocket = _ListeningSocket.Accept( ) ) + { + int availableBytes = 0; + int LoopCount = 0; + //if not all incoming bytes were received by the socket + do + { + if (availableBytes < clientSocket.Available) + { + availableBytes = clientSocket.Available; + LoopCount = 0; + } + else + LoopCount += 1; + Thread.Sleep(5); + } while (availableBytes == 0 || LoopCount < 20); + + if ( availableBytes > 0 ) + { + byte[] buffer = new byte[availableBytes > Settings.MAX_REQUESTSIZE ? Settings.MAX_REQUESTSIZE : availableBytes]; + byte[] header = new byte[0]; + + int readByteCount = clientSocket.Receive(buffer, buffer.Length, SocketFlags.None); + + for ( int headerend = 0; headerend < buffer.Length - 3; headerend++ ) + { + if ( buffer[headerend] == '\r' && buffer[headerend + 1] == '\n' && buffer[headerend + 2] == '\r' && buffer[headerend + 3] == '\n' ) + { + header = new byte[headerend + 4]; + Array.Copy(buffer, 0, header, 0, headerend + 4); + break; + } + } + + //reqeust created, checking the response possibilities + using ( Request tempRequest = new Request(Encoding.UTF8.GetChars(header), clientSocket) ) + { + Debug.Print("... Client connected ... URL: " + tempRequest.URL + " ... Final byte count: " + availableBytes); + + if ( tempRequest.Method == "POST" ) + { + //POST was incoming, it will be saved to SD card at Settings.POST_TEMP_PATH + PostToSdWriter post = new PostToSdWriter(tempRequest, buffer, header.Length); + post.Receive( ); + } + + //Let's check if we have to take some action or if it is a file-response + HandleGETResponses(tempRequest); + } + + Debug.Print("Client loop finished"); + + try + { + //Close client, otherwise the browser / client won't work properly + clientSocket.Close( ); + } + catch ( Exception ex ) + { + Debug.Print(ex.ToString( )); + } + } + } + } + catch ( Exception ex ) + { + Debug.Print(ex.Message); + } + } + } + + /// + /// Checks what Response has to be executed. + /// It compares the requested page URL with the URL set for the coded responses + /// + /// + private void HandleGETResponses(Request e) + { + Response response = null; + + + if ( _Responses.Contains(e.URL) ) + response = (Response)_Responses[e.URL]; + else + response = (Response)_Responses["FileResponse"]; + + + if ( response != null ) + using ( response ) + if ( response.ConditionsCheckAndDataFill(e) ) + { + if ( !response.SendResponse(e) ) + Debug.Print("Sending response failed"); + } + + + Debug.Print("Request handling finnished"); + } + + //------------------------------------------------------------- + //------------------------------------------------------------- + //---------------Webserver expansion--------------------------- + //------------------------------------------------------------- + //------------------------------------------------------------- + //-------------------Basic methods----------------------------- + + /// + /// Adds a Response + /// + /// XMLResponse that has to be added + public void AddResponse(Response response) + { + if ( !_Responses.Contains(response.URL) ) + { + _Responses.Add(response.URL, response); + } + } + + /// + /// Removes a Response + /// + /// XMLResponse that has to be deleted + public void RemoveResponse(String ResponseName) + { + if ( _Responses.Contains(ResponseName) ) + { + _Responses.Remove(ResponseName); + } + } + + //------------------------------------------------------------- + //------------------------------------------------------------- + //-----------------------EXPAND this methods------------------- + + /// + /// Initialize the basic functionalities of the webserver + /// + private void ResponseListInitialize( ) + { + AddResponse(new FileResponse()); + AddResponse(new XMLResponse("echo", new XMLResponseMethod(Echo))); + //AddResponse(new XMLResponse("switchDigitalPin", new XMLResponseMethod(SwitchDigitalPin))); + //AddResponse(new XMLResponse("setDigitalPin", new XMLResponseMethod(SetDigitalPin))); + //AddResponse(new XMLResponse("xmlResponselist", new XMLResponseMethod(ResponseListXML))); + //AddResponse(new JSONResponse("jsonResponselist", new JSONResponseMethod(ResponseListJSON))); + //AddResponse(new XMLResponse("pwm", new XMLResponseMethod(SetPWM))); + //AddResponse(new XMLResponse("getAnalogPinValue", new XMLResponseMethod(GetAnalogPinValue))); + //AddResponse(new XMLResponse("getDigitalPinState", new XMLResponseMethod(GetDigitalPinState))); + //AddResponse(new XMLResponse("multixml", new XMLResponseMethod(MultipleXML))); + //AddResponse(new IndexResponse("")); + //AddResponse(new XMLResponse("upload", new XMLResponseMethod(Upload))); + } + + //------------------------------------------------------------- + //---------------------Expansion Methods----------------------- + //------------------------------------------------------------- + //----------Look at the echo method for xml example------------ + + /// + /// Example for webserver expand method + /// Call via http://servername/echo?value='echovalue' + /// Submit a 'value' GET parameter + /// + /// + /// + /// + private void Echo(Request e, Hashtable results) + { + if ( e.GetArguments.Contains("value") == true ) + results.Add("echo", e.GetArguments["value"]); + else + results.Add("ERROR", "No 'value'-parameter transmitted to server"); + } + + /// + /// Submit a 'pin' GET parameter to switch an OutputPorts state (on/off) + /// + /// + /// + /// + private static void SwitchDigitalPin(Request e, Hashtable h) + { + if ( e.GetArguments.Contains("pin") ) + try + { + int pin = Int32.Parse(e.GetArguments["pin"].ToString( )); + if ( pin >= 0 && pin <= 13 ) + { + //PinManagement.SwitchDigitalPinState(pin); + //h.Add("pin" + pin, PinManagement.GetDigitalPinState(pin) ? "1" : "0"); + } + } + catch + { + h = XMLResponse.GenerateErrorHashtable("pin", ResponseErrorType.ParameterConvertError); + } + else + h = XMLResponse.GenerateErrorHashtable("pin", ResponseErrorType.ParameterMissing); + } + + /// + /// Submit a 'pin' (0-13) and a 'state' (true/false) GET parameter to turn on/off OutputPort + /// + /// + /// + /// + private static void SetDigitalPin(Request e, Hashtable h) + { + if ( e.GetArguments.Contains("pin") ) + if ( e.GetArguments.Contains("state") ) + try + { + int pin = Int32.Parse(e.GetArguments["pin"].ToString( )); + if ( pin >= 0 && pin <= 13 ) + { + try + { + //bool state = ( e.GetArguments["state"].ToString( ) == "true" ) ? true : false; + //PinManagement.SetDigitalPinState(pin, state); + //h.Add("pin" + pin, PinManagement.GetDigitalPinState(pin) ? "1" : "0"); + } + catch + { + h = XMLResponse.GenerateErrorHashtable("state", ResponseErrorType.ParameterRangeException); + } + } + else + h = XMLResponse.GenerateErrorHashtable("pin", ResponseErrorType.ParameterRangeException); + } + catch + { + h = XMLResponse.GenerateErrorHashtable("pin", ResponseErrorType.ParameterConvertError); + } + else + h = XMLResponse.GenerateErrorHashtable("state", ResponseErrorType.ParameterMissing); + else + h = XMLResponse.GenerateErrorHashtable("pin", ResponseErrorType.ParameterMissing); + } + + /// + /// Returns the responses added to the webserver + /// + /// + /// + /// + private void ResponseListXML(Request e, Hashtable h) + { + foreach ( Object k in _Responses.Keys ) + { + if ( _Responses[k] as XMLResponse != null ) + { + h.Add("methodURL", k.ToString( )); + } + } + } + + /// + /// Returns the responses added to the webserver + /// + /// + /// + /// + private void ResponseListJSON(Request e, JsonArray j) + { + JsonObject o; + foreach ( Object k in _Responses.Keys ) + { + if ( _Responses[k] as JSONResponse != null ) + { + o = new JsonObject( ); + o.Add("methodURL", k); + o.Add("methodInternalName", ( (Response)_Responses[k] ).URL); + j.Add(o); + } + } + } + + /// + /// Submit a 'pin' (5,6,9,10), a period and a duration (0 for off, period-value for 100% on) GET parameter to control PWM + /// + /// + /// + /// + private void SetPWM(Request e, Hashtable h) + { + if ( e.GetArguments.Contains("pin") ) + { + if ( e.GetArguments.Contains("period") ) + { + if ( e.GetArguments.Contains("duration") ) + { + try + { + int pin = Int32.Parse(e.GetArguments["pin"].ToString( )); + try + { + uint duration = UInt32.Parse(e.GetArguments["duration"].ToString( )); + try + { + //uint period = UInt32.Parse(e.GetArguments["period"].ToString( )); + //if ( PinManagement.SetPWM(pin, period, duration) ) + // h.Add("success", period + "/" + duration); + //else + // h = XMLResponse.GenerateErrorHashtable("PWM", ResponseErrorType.InternalValueNotSet); + } + catch ( Exception ex ) + { + h = XMLResponse.GenerateErrorHashtable("period", ResponseErrorType.ParameterConvertError); + Debug.Print(ex.ToString( )); + } + } + catch ( Exception ex ) + { + h = XMLResponse.GenerateErrorHashtable("duration", ResponseErrorType.ParameterConvertError); + Debug.Print(ex.ToString( )); + } + } + catch ( Exception ex ) + { + h = XMLResponse.GenerateErrorHashtable("pin", ResponseErrorType.ParameterConvertError); + Debug.Print(ex.ToString( )); + } + } + else + h = XMLResponse.GenerateErrorHashtable("duration", ResponseErrorType.ParameterMissing); + } + else + h = XMLResponse.GenerateErrorHashtable("period", ResponseErrorType.ParameterMissing); + } + else + h = XMLResponse.GenerateErrorHashtable("pin", ResponseErrorType.ParameterMissing); + } + + /// + /// Submit a 'pin' (0-13) GET parameter. Returns true or false + /// + /// + /// + /// + private void GetDigitalPinState(Request e, Hashtable h) + { + if ( e.GetArguments.Contains("pin") ) + { + try + { + //int pin = Int32.Parse(e.GetArguments["pin"].ToString( )); + //h.Add("pin" + pin, PinManagement.GetDigitalPinState(pin) ? "1" : "0"); + } + catch ( Exception ex ) + { + h = XMLResponse.GenerateErrorHashtable("pin", ResponseErrorType.ParameterConvertError); + Debug.Print(ex.ToString( )); + } + } + else + h = XMLResponse.GenerateErrorHashtable("pin", ResponseErrorType.ParameterMissing); + } + + /// + /// Submit a 'pin' (0-5) GET parameter. Returns true or false + /// + /// + /// + /// + private void GetAnalogPinValue(Request e, Hashtable h) + { + if ( e.GetArguments.Contains("pin") ) + { + try + { + //int pin = Int32.Parse(e.GetArguments["pin"].ToString( )); + //h.Add("pin" + pin, PinManagement.GetAnalogPinValue(pin)); + } + catch ( Exception ex ) + { + h = XMLResponse.GenerateErrorHashtable("pin", ResponseErrorType.ParameterConvertError); + Debug.Print(ex.ToString( )); + } + } + else + h = XMLResponse.GenerateErrorHashtable("pin", ResponseErrorType.ParameterMissing); + } + + /// + /// Example for the useage of the new XML library + /// Use the hashtable if you don't need nested XML (like the standard xml responses) + /// If you need nested XML, use the XMLPair class. The Key-parameter is String. + /// As value the following types can be used to achieve nesting: XMLPair, XMLPair[] and Hashtable + /// + /// + /// + /// + private void MultipleXML(Request e, Hashtable returnHashtable) + { + returnHashtable.Add("UseTheHashtable", "If you don't need nested XML"); + + XMLList Phones = new XMLList("Phones"); + Phones.Attributes.Add("ExampleAttribute1", "NeonMika"); + Phones.Attributes.Add("ExampleAttribute2", 1992); + XMLList BluePhones = new XMLList("BluePhones"); + XMLList BlackPhones = new XMLList("BlackPhones"); + XMLList MokiaRumia = new XMLList("Phone"); + XMLList LangsumTalaxy = new XMLList("Phone"); + MokiaRumia.Add(new XMLPair("Name", "Mokia Rumia")); + MokiaRumia.Add(new XMLPair("PhoneNumber", 436603541897)); + XMLList WirelessConnections = new XMLList("WirelessConnections"); + WirelessConnections.Add(new XMLPair("WLAN", true)); + WirelessConnections.Add(new XMLPair("Bluetooth", false)); + MokiaRumia.Add(WirelessConnections); + WirelessConnections.Clear( ); + WirelessConnections.Add(new XMLPair("WLAN", false)); + WirelessConnections.Add(new XMLPair("Bluetooth", true)); + LangsumTalaxy.Add(new XMLPair("Name", "Langsum Talaxy")); + LangsumTalaxy.Add(new XMLPair("PhoneNumber", 436603541122)); + LangsumTalaxy.Add(WirelessConnections); + + Phones.Add(MokiaRumia); + Phones.Add(LangsumTalaxy); + + returnHashtable.Add("Phones", Phones); + } + + private void Upload(Request e, Hashtable ret) + { + if ( e.GetArguments.Contains("path") ) + { + try + { + string path = e.GetArguments["path"].ToString( ); + path.Replace('/', '\\'); + + try + { + string dir = path.Substring(0, path.LastIndexOf("\\")); + Directory.CreateDirectory(dir); + } + catch ( Exception ex ) + { + Debug.Print(ex.ToString( )); + } + + try + { + FileStream fs = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write); + Debug.Print(Debug.GC(true).ToString( )); + Debug.Print(Debug.GC(true).ToString( )); + PostFileReader post = new PostFileReader( ); + Debug.Print(Debug.GC(true).ToString( )); + Debug.Print(Debug.GC(true).ToString( )); + for ( int i = 0; i < post.Length / Settings.FILE_BUFFERSIZE; i++ ) + fs.Write(post.Read(Settings.FILE_BUFFERSIZE), 0, Settings.FILE_BUFFERSIZE); + fs.Write(post.Read((int)( post.Length % Settings.FILE_BUFFERSIZE )), 0, (int)( post.Length % Settings.FILE_BUFFERSIZE )); + fs.Flush( ); + fs.Close( ); + post.Close( ); + } + catch ( Exception ex ) + { + Debug.Print(ex.ToString( )); + ret = XMLResponse.GenerateErrorHashtable("file access", ResponseErrorType.InternalOperationError); + } + + } + catch ( Exception ex ) { Debug.Print(ex.ToString( )); } + } + else + ret = XMLResponse.GenerateErrorHashtable("path", ResponseErrorType.ParameterMissing); + } + } +} diff --git a/WebServer.NM/NeonMika.NETMF.Webserver/Settings.cs b/WebServer.NM/NeonMika.NETMF.Webserver/Settings.cs new file mode 100644 index 0000000..461545f --- /dev/null +++ b/WebServer.NM/NeonMika.NETMF.Webserver/Settings.cs @@ -0,0 +1,35 @@ + +namespace NeonMika.Webserver +{ + /// + /// Static settings for the webserver + /// + static class Settings + { + /// + /// Maximum byte size for a HTTP request sent to the server + /// POST packages will get split up into smaller packages this size + /// + public const int MAX_REQUESTSIZE = 1024; + + /// + /// Buffersize for response file sending + /// + public const int FILE_BUFFERSIZE = 512; + + /// + /// Path to save POST arguments temporarly + /// + public const string POST_TEMP_PATH = "\\SD\\lastPOST"; + + /// + /// Root path for webserver on the SD card. + /// + public const string ROOT_SD_PATH = "\\SD\\"; + + /// + /// Default page for when root is requested. + /// + public const string DEFAULT_PAGE = "index.html"; + } +} diff --git a/WebServer.NM/NeonMika.Util/Converter.cs b/WebServer.NM/NeonMika.Util/Converter.cs new file mode 100644 index 0000000..c4885dc --- /dev/null +++ b/WebServer.NM/NeonMika.Util/Converter.cs @@ -0,0 +1,24 @@ +using System; +using Microsoft.SPOT; +using System.Collections; +using System.IO; + +namespace NeonMika.Util +{ + public static class Converter + { + public static Hashtable ToHashtable(string[] lines, string seperator, int startAtLine = 0) + { + Hashtable toReturn = new Hashtable( ); + string[] line; + for ( int i = startAtLine; i < lines.Length; i++ ) + { + line = lines[i].EasySplit(seperator); + if ( line.Length > 1 ) + toReturn.Add(line[0], line[1]); + + } + return toReturn; + } + } +} diff --git a/WebServer.NM/NeonMika.Util/ExtensionAttribute.cs b/WebServer.NM/NeonMika.Util/ExtensionAttribute.cs new file mode 100644 index 0000000..a579b2f --- /dev/null +++ b/WebServer.NM/NeonMika.Util/ExtensionAttribute.cs @@ -0,0 +1,10 @@ +using System; + +namespace System.Runtime.CompilerServices +{ + [AttributeUsage(AttributeTargets.Assembly | + AttributeTargets.Class | + AttributeTargets.Method)] + public sealed class ExtensionAttribute : Attribute + { } +} \ No newline at end of file diff --git a/WebServer.NM/NeonMika.Util/ExtensionMethods.cs b/WebServer.NM/NeonMika.Util/ExtensionMethods.cs new file mode 100644 index 0000000..bffeed8 --- /dev/null +++ b/WebServer.NM/NeonMika.Util/ExtensionMethods.cs @@ -0,0 +1,39 @@ +using System; +using Microsoft.SPOT; + +namespace NeonMika.Util +{ + public static class ExtensionMethods + { + public static string[] EasySplit(this string s, string seperator) + { + int pos = s.IndexOf(seperator); + if (pos != -1) + return new string[] { s.Substring(0, pos).Trim(new char[] { ' ', '\n', '\r' }), s.Substring(pos + seperator.Length, s.Length - pos - seperator.Length).Trim(new char[] { ' ', '\n', '\r' }) }; + else + return new string[] { s.Trim(new char[] { ' ', '\n', '\r' }) }; + } + + public static bool StartsWith(this string s, string start) + { + for (int i = 0; i < start.Length; i++) + if (s[i] != start[i]) + return false; + + return true; + } + + public static void Replace(this string s, char replaceThis, char replaceWith) + { + string temp = ""; + for (int i = 0; i < s.Length; i++) + { + if (s[i] == replaceThis) + temp += replaceWith; + else + temp += s[i]; + } + s = temp; + } + } +} diff --git a/WebServer.NM/NeonMika.Util/NeonMika.Util.csproj b/WebServer.NM/NeonMika.Util/NeonMika.Util.csproj new file mode 100644 index 0000000..f7459b3 --- /dev/null +++ b/WebServer.NM/NeonMika.Util/NeonMika.Util.csproj @@ -0,0 +1,45 @@ + + + + NeonMika.Util + Library + NeonMika.Util + {b69e3092-b931-443c-abe7-7e7b65f2a37f};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 9.0.21022 + 2.0 + {A0B9E75A-9052-4140-A86A-9AC82B484F98} + v4.2 + $(MSBuildExtensionsPath32)\Microsoft\.NET Micro Framework\ + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebServer.NM/NeonMika.Util/Properties/AssemblyInfo.cs b/WebServer.NM/NeonMika.Util/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..9bcd597 --- /dev/null +++ b/WebServer.NM/NeonMika.Util/Properties/AssemblyInfo.cs @@ -0,0 +1,25 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("NeonMika.Util")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("NeonMika.Util")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2012")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/WebServer.NM/NeonMika.Util/Stopwatch.cs b/WebServer.NM/NeonMika.Util/Stopwatch.cs new file mode 100644 index 0000000..5782c73 --- /dev/null +++ b/WebServer.NM/NeonMika.Util/Stopwatch.cs @@ -0,0 +1,101 @@ +using System; +using Microsoft.SPOT; + +namespace NeonMika.Util +{ + /* + * Thanks to Chris Walker and Gilberto Garcia for this code! + * */ + public class Stopwatch + { + private long m_startTicks = 0; + private long m_stopTicks = 0; + private bool m_isRunning = false; + + private const long m_ticksPerMillisecond = System.TimeSpan.TicksPerMillisecond; + + public static Stopwatch StartNew() + { + Stopwatch stopwatch = new Stopwatch(); + stopwatch.Start(); + return stopwatch; + } + + private Stopwatch() + { + } + + public void Reset() + { + m_startTicks = 0; + m_stopTicks = 0; + m_isRunning = false; + } + + public void Start() + { + if (m_startTicks != 0 && m_stopTicks != 0) + m_startTicks = Microsoft.SPOT.Hardware.Utility.GetMachineTime().Ticks - (m_stopTicks - m_startTicks); // resume existing timer + else + m_startTicks = Microsoft.SPOT.Hardware.Utility.GetMachineTime().Ticks; // start new timer + m_isRunning = true; + } + + public void Stop() + { + m_stopTicks = Microsoft.SPOT.Hardware.Utility.GetMachineTime().Ticks; + m_isRunning = false; + } + + public long ElapsedMilliseconds + { + get + { + if (m_startTicks != 0 && m_isRunning) + return (Microsoft.SPOT.Hardware.Utility.GetMachineTime().Ticks - m_startTicks) / m_ticksPerMillisecond; + else if (m_startTicks != 0 && !m_isRunning) + return (m_stopTicks - m_startTicks) / m_ticksPerMillisecond; + else + throw new InvalidOperationException(); + } + } + + public double ElapsedSeconds + { + get + { + if (m_startTicks != 0 && m_isRunning) + { + TimeSpan duration = new TimeSpan((Microsoft.SPOT.Hardware.Utility.GetMachineTime().Ticks - m_startTicks)); + return duration.Seconds; + } + else if (m_startTicks != 0 && !m_isRunning) + { + TimeSpan duration = new TimeSpan((m_stopTicks - m_startTicks)); + return duration.Seconds; + } + else + throw new InvalidOperationException(); + } + } + + public double ElapsedMinutes + { + get + { + if (m_startTicks != 0 && m_isRunning) + { + TimeSpan duration = new TimeSpan((Microsoft.SPOT.Hardware.Utility.GetMachineTime().Ticks - m_startTicks)); + return duration.Minutes; + } + else if (m_startTicks != 0 && !m_isRunning) + { + TimeSpan duration = new TimeSpan((m_stopTicks - m_startTicks)); + return duration.Minutes; + } + else + throw new InvalidOperationException(); + } + } + } +} \ No newline at end of file diff --git a/WebServer.NM/NeonMika.XML/NeonMika.XML.csproj b/WebServer.NM/NeonMika.XML/NeonMika.XML.csproj new file mode 100644 index 0000000..e9565fe --- /dev/null +++ b/WebServer.NM/NeonMika.XML/NeonMika.XML.csproj @@ -0,0 +1,43 @@ + + + + NeonMika.XML + Library + NeonMika.XML + {b69e3092-b931-443c-abe7-7e7b65f2a37f};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 9.0.21022 + 2.0 + {0AD21E9F-0370-40B6-B045-109A266C9FC2} + v4.2 + $(MSBuildExtensionsPath32)\Microsoft\.NET Micro Framework\ + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + \ No newline at end of file diff --git a/WebServer.NM/NeonMika.XML/Properties/AssemblyInfo.cs b/WebServer.NM/NeonMika.XML/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..84d1521 --- /dev/null +++ b/WebServer.NM/NeonMika.XML/Properties/AssemblyInfo.cs @@ -0,0 +1,25 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("NeonMika.XML")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("NeonMika.XML")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2012")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/WebServer.NM/NeonMika.XML/XMLList.cs b/WebServer.NM/NeonMika.XML/XMLList.cs new file mode 100644 index 0000000..d95e6fd --- /dev/null +++ b/WebServer.NM/NeonMika.XML/XMLList.cs @@ -0,0 +1,54 @@ +using System; +using Microsoft.SPOT; +using System.Collections; + +namespace NeonMika.XML +{ + public class XMLList : ArrayList, XMLObject + { + public XMLList(string key) + { + Key = key; + Attributes = new Hashtable( ); + } + + public override string ToString( ) + { + string attributes = ""; + foreach ( object o in Attributes.Keys ) + attributes += " " + o.ToString( ) + "=\"" + Attributes[o] + "\""; + + string s = "<"+Key+attributes+">"; + + for ( int i = 0; i < Count; i++ ) + s += this[i].ToString( ); + + return s + ""; + } + + #region XMLObject Members + + public string Key + { + get; + set; + } + + /// + /// Don't use Value, work with the list itself! + /// + public object Value + { + get { return null; } + set { } + } + + public Hashtable Attributes + { + get; + set; + } + + #endregion + } +} diff --git a/WebServer.NM/NeonMika.XML/XMLObject.cs b/WebServer.NM/NeonMika.XML/XMLObject.cs new file mode 100644 index 0000000..45429eb --- /dev/null +++ b/WebServer.NM/NeonMika.XML/XMLObject.cs @@ -0,0 +1,13 @@ +using System; +using System.Text; +using System.Collections; + +namespace NeonMika.XML +{ + interface XMLObject + { + string Key { get; set; } + object Value { get; set; } + Hashtable Attributes { get; set; } + } +} diff --git a/WebServer.NM/NeonMika.XML/XMLPair.cs b/WebServer.NM/NeonMika.XML/XMLPair.cs new file mode 100644 index 0000000..c94936d --- /dev/null +++ b/WebServer.NM/NeonMika.XML/XMLPair.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections; + +namespace NeonMika.XML +{ + public class XMLPair : XMLObject + { + public XMLPair(string Key, object Value) + { + this.Key = Key; + this.Value = Value; + this.Attributes = new Hashtable(); + } + + public XMLPair(string Key) + { + this.Key = Key; + this.Value = null; + this.Attributes = new Hashtable(); + } + + public override string ToString() + { + string attributes = ""; + foreach ( object o in Attributes.Keys ) + attributes += " " + o.ToString( ) + "=\"" + Attributes[o] + "\""; + + string s = "<" + Key + attributes + ">"; + s += Value.ToString( ); + s += ""; + return s; + } + + #region XMLObject Members + + public string Key + { + get; + set; + } + + public object Value + { + get; + set; + } + + public Hashtable Attributes + { + get; + set; + } + + #endregion + } +} diff --git a/WebServer/EventArgs/RequestReceivedEventArgs.cs b/WebServer/EventArgs/RequestReceivedEventArgs.cs new file mode 100644 index 0000000..e9f008f --- /dev/null +++ b/WebServer/EventArgs/RequestReceivedEventArgs.cs @@ -0,0 +1,44 @@ +using System; +using System.Text; +using System.Net.Sockets; +using System.Net; + +namespace Webserver.EventArgs +{ + public class RequestReceivedEventArgs + { + private DateTime receiveTime; + private Request request; + private Socket client; + private int byteCount; + + public DateTime ReceiveTime + { + get { return receiveTime; } + } + + public Request Request + { + get { return request; } + } + + public Socket Client + { + get { return client; } + } + + public int ByteCount + { + get { return byteCount; } + } + + public RequestReceivedEventArgs(Request request, Socket client, int byteCount) + { + this.request = request; + this.client = client; + this.byteCount = byteCount; + + this.receiveTime = DateTime.Now; + } + } +} diff --git a/WebServer/HttpGeneral.cs b/WebServer/HttpGeneral.cs deleted file mode 100644 index 4ec7bdb..0000000 --- a/WebServer/HttpGeneral.cs +++ /dev/null @@ -1,172 +0,0 @@ -using System; -using System.Text; -using Microsoft.SPOT; -using System.Collections; - -public class HttpGeneral -{ - //HTTP/1.1 200 OK - //Date: Sun, 07 Aug 2011 06:01:05 GMT - //Server: Apache/2 - //Last-Modified: Wed, 01 Sep 2004 13:24:52 GMT - //ETag: ""805b-3e3073913b100"" - //Accept-Ranges: bytes - //Cache-Control: max-age=21600 - //Expires: Sun, 07 Aug 2011 12:01:05 GMT - //Vary: Accept-Encoding - //Content-Encoding: gzip - //P3P: policyref=""http://www.w3.org/2001/05/P3P/p3p.xml"" - //Content-Length: 5605 - //Connection: close - //Content-Type: text/html; charset=iso-8859-1 - - public static string GetHttpHeader(long length, string type, int maxAge) - { - string header = "HTTP/1.1 200 OK\r\nContent-Type: " + type + "; charset=utf-8\r\nAccess-Control-Allow-Origin: *\r\nCache-Control: max-age=" + maxAge.ToString() + ", must-revalidate\r\nContent-Length: " + length.ToString() + "\r\nConnection: close\r\n\r\n"; - return header; - } - - - /// - /// Send a Not Found response - /// - public static string Get404Header(long contentLength) - { - Debug.Print("Sending 404 Not Found"); - string headerString = "HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\n"; - return headerString; - } - - /// - /// Send a Server Error 500 response - /// - public static string Get500Header(long contentLength) - { - Debug.Print("Sending 500 server error."); - string headerString = "HTTP/1.1 500 server error.\r\nContent-Length: " + contentLength.ToString() + "\r\nConnection: close\r\n\r\n"; - return headerString; - } - - - public static Hashtable ParseQuerystring(string _uri, out string _baseUri) - { - _baseUri = _uri; - Hashtable result = new Hashtable(); - if (_uri != null) - { - string[] split = _uri.Split('?'); - if (split.Length > 1) - { - _baseUri = split[0]; - string partQueryString = split[1]; - result = GetEncodedVariables(partQueryString); - } - } - return result; - } - - private static Hashtable GetEncodedVariables(string variableString) - { - Hashtable result = new Hashtable(); - if (variableString != null) - { - string[] split = variableString.Split('&'); - if (split.Length > 0) - {// remove querystring ? - split[0] = split[0].TrimStart(new char[] { '?', ' ' }); - } - for (int i = 0; i < split.Length; i++) - { - string[] parameterArray = split[i].Split('='); - - string value = ""; - if (parameterArray.Length > 1) - { - value = parameterArray[1].Trim(); - value = UriDecode(value); - } - string key = parameterArray[0].Trim(); - if (!result.Contains(key)) - { - result.Add(key, value); - } - } - } - return result; - } - - private static string UriDecode(string s) - { - if (s == null) return null; - if (s.Length < 1) return s; - - char[] chars = s.ToCharArray(); - byte[] bytes = new byte[chars.Length * 2]; - int count = chars.Length; - int dstIndex = 0; - int srcIndex = 0; - - while (true) - { - if (srcIndex >= count) - { - if (dstIndex < srcIndex) - { - byte[] sizedBytes = new byte[dstIndex]; - Array.Copy(bytes, 0, sizedBytes, 0, dstIndex); - bytes = sizedBytes; - } - return new string(Encoding.UTF8.GetChars(bytes)); - } - - if (chars[srcIndex] == '+') - { - bytes[dstIndex++] = (byte)' '; - srcIndex += 1; - } - else if (chars[srcIndex] == '%' && srcIndex < count - 2) - if (chars[srcIndex + 1] == 'u' && srcIndex < count - 5) - { - int ch1 = HexToInt(chars[srcIndex + 2]); - int ch2 = HexToInt(chars[srcIndex + 3]); - int ch3 = HexToInt(chars[srcIndex + 4]); - int ch4 = HexToInt(chars[srcIndex + 5]); - - if (ch1 >= 0 && ch2 >= 0 && ch3 >= 0 && ch4 >= 0) - { - bytes[dstIndex++] = (byte)((ch1 << 4) | ch2); - bytes[dstIndex++] = (byte)((ch3 << 4) | ch4); - srcIndex += 6; - continue; - } - } - else - { - int ch1 = HexToInt(chars[srcIndex + 1]); - int ch2 = HexToInt(chars[srcIndex + 2]); - - if (ch1 >= 0 && ch2 >= 0) - { - bytes[dstIndex++] = (byte)((ch1 << 4) | ch2); - srcIndex += 3; - continue; - } - } - else - { - byte[] charBytes = Encoding.UTF8.GetBytes(chars[srcIndex++].ToString()); - charBytes.CopyTo(bytes, dstIndex); - dstIndex += charBytes.Length; - } - } - } - - private static int HexToInt(char ch) - { - return - (ch >= '0' && ch <= '9') ? ch - '0' : - (ch >= 'a' && ch <= 'f') ? ch - 'a' + 10 : - (ch >= 'A' && ch <= 'F') ? ch - 'A' + 10 : - -1; - } -} diff --git a/WebServer/Listener.cs b/WebServer/Listener.cs deleted file mode 100644 index 3680ab5..0000000 --- a/WebServer/Listener.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using Microsoft.SPOT; -using System.Net.Sockets; -using System.Net; -using System.Text; -using System.Threading; - -namespace WebServer -{ - public delegate void RequestReceivedDelegate(Request request); - - public class Listener : IDisposable - { - const int MaxRequestSize = 1024; - readonly int m_port = 80; - - private Socket m_listeningSocket = null; - private RequestReceivedDelegate m_requestReceived; - - public Listener(RequestReceivedDelegate _requestReceived) - : this(_requestReceived, 80) { } - - public Listener(RequestReceivedDelegate _requestReceived, int _port) - { - m_port = _port; - m_requestReceived = _requestReceived; - m_listeningSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - m_listeningSocket.Bind(new IPEndPoint(IPAddress.Any, m_port)); - m_listeningSocket.Listen(10); - } - - ~Listener() { Dispose(); } - - public void Dispose() - { - if (m_listeningSocket != null) m_listeningSocket.Close(); - } - - public void Listen() - { - while (true) - { - using (Socket clientSocket = m_listeningSocket.Accept()) - { - IPEndPoint clientIP = clientSocket.RemoteEndPoint as IPEndPoint; - Debug.Print("Received request from " + clientIP.ToString()); - - int availableBytes = clientSocket.Available; - Debug.Print(DateTime.Now.ToString() + " " + availableBytes.ToString() + " request bytes available"); - - int bytesReceived = (availableBytes > MaxRequestSize ? MaxRequestSize : availableBytes); - if (bytesReceived > 0) - { - byte[] buffer = new byte[bytesReceived]; // Buffer probably should be larger than this. - int readByteCount = clientSocket.Receive(buffer, bytesReceived, SocketFlags.None); - - using (Request r = new Request(clientSocket, buffer)) - { - Debug.Print(DateTime.Now.ToString() + " " + r.Uri); - if (m_requestReceived != null) m_requestReceived(r); - - } - } - } - // I always like to have this in a continuous loop. Helps prevent lock-ups - Thread.Sleep(10); - } - } - } -} diff --git a/WebServer/OldRequest.cs b/WebServer/OldRequest.cs deleted file mode 100644 index d84799b..0000000 --- a/WebServer/OldRequest.cs +++ /dev/null @@ -1,328 +0,0 @@ -using System; -using Microsoft.SPOT; -using System.Text; -using System.Threading; -using System.Collections; -using Microsoft.SPOT.Hardware; -using SecretLabs.NETMF.Hardware; -using System.Net.Sockets; - -namespace WebServer -{ - public enum EnumContentType { Text, Binary, MultipartFormData } //add as neccessary - public enum EnumRequestType { Get, Post, Put } //add as neccessary - - public class BaseRequest - { - public EnumRequestType HttpRequestType { get; protected set; } - public string Uri { get; protected set; } - public string BaseUri { get; protected set; } - public string HttpVersion { get; protected set; } - public string Host { get; protected set; } - public string UserAgent { get; protected set; } - public Hashtable HeaderFields { get; protected set; } - public Hashtable QuerystringVariables { get; protected set; } - - - protected BaseRequest() - { - } - - public BaseRequest(byte[] bRawRequest, int contentStart, int contentSize, Hashtable headerFields, EnumRequestType requestType) - { - LoadBaseRequest(ref bRawRequest, contentStart, contentSize, headerFields, requestType); - } - - protected void LoadBaseRequest(ref byte[] bRawRequest, int contentStart, int contentSize, Hashtable headerFields, EnumRequestType requestType) - { - HttpRequestType = requestType; - HeaderFields = headerFields; - // Convert to string, will include HTTP headers. - string content = new string(Encoding.UTF8.GetChars(bRawRequest, contentStart, contentSize));// there shouldn't be content - - // pull out uri - Uri = headerFields["Uri"].ToString();// url is deprecated? - string baseUri = ""; - QuerystringVariables = GetQueryStringVariables(Uri, out baseUri); - BaseUri = baseUri; - //parse uri for command processing. - //start with the 2nd character to avoid a blank entry. - // ParsedUri = Uri.Substring(1).Split(new char[] { '/' }); - } - - public static void LoadHeader(byte[] rawRequest, int rawRequestSize, out int headerLength, out Hashtable header, out EnumRequestType requestType) // returns the start position of the unused bytes - { - header = new Hashtable(); - requestType = GetRequestType(ref rawRequest); - string requestString = new string(Encoding.UTF8.GetChars(rawRequest, 0, rawRequestSize)); - headerLength = GetHeaderLength(ref rawRequest); - string[] headerArray = requestString.Split('\n'); - if (headerArray.Length > 1) - { - string[] split = headerArray[0].Split(' '); - if (split.Length >= 2) - { - // first line GET /index.html?userid=joe&password=guessme HTTP/1.1 - header.Add("Uri", split[1]); - } - if (split.Length >= 3) - { - header.Add("HttpVersion", split[2]); - } - - } - for (int i = 1; i < headerArray.Length; i++) - { - AddFieldToHashTable(header, headerArray[i]); - } - } - - protected static int GetHeaderLength(ref byte[] buffer) - { - int contentStart = -1; - for (int i = 0; i < buffer.Length - 3; i++) - {// find a double "carriage return", "line feed" to give the end of the header, "0xd, 0xa, 0xd, 0xa" - if (buffer[i] == 0xd) - { - if (buffer[i + 1] == 0xa) - { - if (buffer[i + 2] == 0xd) - { - if (buffer[i + 3] == 0xa) - { - contentStart = i + 4; - break; - } - } - } - } - } - return contentStart; - } - - protected static void AddFieldToHashTable(Hashtable fields, string keyValuePair) // Todo fix, because User-Agent and other fields may have spaces - { - string[] split = keyValuePair.Split(new char[] { ':'},2); - if (split.Length > 1) - { - if (split[0] == "Content-Type") - { - split = keyValuePair.Split(new char[] { ':', ';', '=' });// todo refactor - int numberOfPairs = split.Length / 2; - for (int i = 0; i < numberOfPairs; i++) - { - //Host: www.mysite.com - //User-Agent: Mozilla/4.0 - //"Content-Type: multipart/form-data; boundary=---------------------------41184676334\r" - string key = split[2 * i].Trim(new char[] { ' ', '\r' }); - string value = split[2 * i + 1].Trim(new char[] { ' ', '\r' }); - { - if (!fields.Contains(key))// don't add twice it may raise inconvenient exception - { - fields.Add(key, value); - Debug.Print(key + " : " + value); - } - } - } - } - else - { - string key = split[0]; - string value = split[1].Trim(new char[] { '\r' }); - { - if (!fields.Contains(key))// don't add twice it may raise inconvenient exception - { - fields.Add(key, value); - Debug.Print(key + " : " + value); - } - } - } - } - } - - protected Hashtable GetQueryStringVariables(string uri, out string baseUrl) - { - baseUrl = uri; - Hashtable result = new Hashtable(); - if (uri != null) - { - string[] split = uri.Split('?'); - if (split.Length > 1) - { - baseUrl = split[0]; - string partQueryString = split[1]; - result = GetEncodedVariables(partQueryString); - } - - } - return result; - } - - protected Hashtable GetEncodedVariables(string variableString) - { - Hashtable result = new Hashtable(); - if (variableString != null) - { - string[] split = variableString.Split('&'); - if (split.Length > 0) - {// remove querystring ? - split[0] = split[0].TrimStart(new char[] { '?', ' ' }); - } - for (int i = 0; i < split.Length; i++) - { - string[] parameterArray = split[i].Split('='); - - string value = ""; - if (parameterArray.Length > 1) - { - value = parameterArray[1].Trim(); - value = HttpGeneral.UriDecode(value); - } - string key = parameterArray[0].Trim(); - if (!result.Contains(key)) - { - result.Add(key, value); - } - } - } - return result; - } - - private static EnumRequestType GetRequestType(ref byte[] bRawRequest) - { - string rawRequestType = new string(Encoding.UTF8.GetChars(bRawRequest, 0, 20)); - rawRequestType = rawRequestType.TrimStart().ToUpper(); - if (rawRequestType.IndexOf("GET") == 0) - { - return EnumRequestType.Get; - } - else - { - if (rawRequestType.IndexOf("PUT") == 0) - { - return EnumRequestType.Put; - } - else - { - return EnumRequestType.Post; - } - } - } - } - - //public class GetRequest:BaseRequest - //{ - // //GET /index.html?userid=joe&password=guessme HTTP/1.1 - // //Host: www.mysite.com - // //User-Agent: Mozilla/4.0 - - // public GetRequest(byte[] buffer, int dataStart, int dataEnd) : base(buffer, dataLength) - // { - // IsHttpGet = true; - // HeaderFields = GetHeader(buffer, dataLength); - // } - //} - - - public class PostRequest : BaseRequest - { - public int ContentLength { get; private set; } - public string ContentType { get; private set; } - public EnumContentType ContentTypeGroup { get; private set; } - public int ContentStartPosition { get; private set; } - public Hashtable ContentVariables { get; protected set; } - public Hashtable ContentVariablesBinary { get; protected set; } - public GetMoreBytesHandler GetMoreBytesHandler = null; - public Socket ConnectionSocket = null;// todo can make private? - - //POST /login.jsp HTTP/1.1 - //Host: www.mysite.com - //User-Agent: Mozilla/4.0 - //Content-Length: 27 - //Content-Type: application/x-www-form-uriencoded - - //userid=joe&password=guessme - - - - //or also - //Content-Type: application/octet-stream for binary - - public PostRequest(byte[] bRawRequest, int contentStart, int contentSize, Hashtable headerFields, EnumRequestType requestType, Socket connectionSocket, GetMoreBytesHandler getMoreBytesHandler) - : base(bRawRequest, contentStart, contentSize, headerFields, requestType) - { - ContentTypeGroup = EnumContentType.Text;// todo check this maybe remove - GetMoreBytesHandler = getMoreBytesHandler; - ConnectionSocket = connectionSocket; - ContentStartPosition = contentStart; - ContentLength = contentSize; - // pull out uri - Uri = headerFields["Uri"].ToString(); - ContentType = HeaderFields["Content-Type"].ToString(); - if(HeaderFields.Contains("Content-Length")) - { - int contentLengthFromHeader = int.Parse(HeaderFields["Content-Length"].ToString()); - if( contentLengthFromHeader != ContentLength) - { - ; // means more packets needed - } - } - - if (ContentTypeGroup == EnumContentType.Text) - { - string content = new string(Encoding.UTF8.GetChars(bRawRequest, ContentStartPosition, ContentLength - ContentStartPosition)); - //ContentVariables = GetEncodedVariables(content); - } - - - //if (ContentType.ToLower().IndexOf("multipart/form-data") >= 0) - //{ // http://www.4guysfromrolla.com/webtech/091201-1.shtml check filesize before uploading - // byte[] boundary = null;// todo get this. - // int boundaryLength = -1; - // int[] boundaryDelimiterPositions = null; - // ContentTypeGroup = EnumContentType.MultipartFormData; - // GetMultipartFormTextVariables(buffer, ContentStartPosition, boundary, out boundaryDelimiterPositions, out boundaryLength); - // ContentVariables = GetMultipartFormTextVariables(buffer, boundaryDelimiterPositions, boundaryLength); - // ContentVariablesBinary = GetMultipartFormBinaryVariables(buffer, boundaryDelimiterPositions, boundaryLength); - //} - //else - //{ - // if (ContentType.ToLower().IndexOf("application/octet-stream") >= 0) - // { - // ContentTypeGroup = EnumContentType.Binary; - // throw new NotImplementedException("application/octet-stream not implemented."); - // } - // else - // { - // ContentTypeGroup = EnumContentType.Text; - // //Content-Type: application/x-www-form-urlencoded Todo are there any other options? - // string content = new string(Encoding.UTF8.GetChars(buffer, ContentStartPosition, dataLength - ContentStartPosition)); - // ContentVariables = GetEncodedVariables(content); - // } - //} - - - } - - public void GetMultipartFormTextVariables(byte[] data, int ContentStartPosition, byte[] boundary, out int[] boundaryDelimiterPositions, out int boundaryLength) - { - boundaryDelimiterPositions = null;// TODO - boundaryLength = -1; - } - - - public Hashtable GetMultipartFormTextVariables(byte[] data, int[] boundaryDelimiterPositions, int boundaryLength) - { - Hashtable result = new Hashtable();// TODO -// string content = new string(Encoding.UTF8.GetChars(bRawRequest, ContentStartPosition, bRawRequest.Length - ContentStartPosition)); - return result; - } - - public Hashtable GetMultipartFormBinaryVariables(byte[] data, int[] boundaryDelimiterPositions, int boundaryLength) - { - Hashtable result = new Hashtable();// TODO -// string content = new string(Encoding.UTF8.GetChars(bRawRequest, ContentStartPosition, bRawRequest.Length - ContentStartPosition)); - return result; - } - } -} \ No newline at end of file diff --git a/WebServer/POST/PostFileReader.cs b/WebServer/POST/PostFileReader.cs new file mode 100644 index 0000000..8674830 --- /dev/null +++ b/WebServer/POST/PostFileReader.cs @@ -0,0 +1,41 @@ +using System; +using Microsoft.SPOT; +using System.IO; +using Webserver; + +namespace NeonMika.Webserver.POST +{ + public class PostFileReader : IDisposable + { + FileStream fs; + + public PostFileReader() + { + fs = new FileStream(Settings.LAST_POST, FileMode.Open); + } + + public byte[] Read(int count) + { + byte[] arr = new byte[count]; + fs.Read(arr, 0, count); + return arr; + } + + public long Length { get { return fs.Length; } } + + public void Close() + { + try { fs.Close(); } + catch (Exception ex) { } + } + + #region IDisposable Members + + public void Dispose() + { + Close(); + } + + #endregion + } +} diff --git a/WebServer/POST/PostToSdWriter.cs b/WebServer/POST/PostToSdWriter.cs new file mode 100644 index 0000000..c0ae65f --- /dev/null +++ b/WebServer/POST/PostToSdWriter.cs @@ -0,0 +1,61 @@ +using System; +using Microsoft.SPOT; +using System.IO; +using System.Threading; +using System.Net.Sockets; +using System.Text; +using Webserver.Responses; +using Webserver; + +namespace NeonMika.Webserver.POST +{ + /// + /// Saves a POST-request at Setting.POST_TEMP_PATH + /// Sends back "OK" on success + /// + class PostToSdWriter : IDisposable + { + private byte[] _buffer; + private Request _e; + + public PostToSdWriter(Request e, byte[] buffer) + { + _buffer=buffer; + _e = e; + } + + /// + /// Saves content to Setting.POST_TEMP_PATH + /// + /// The request which should be handeld + /// True if 200_OK was sent, otherwise false + public bool Receive() + { + Debug.Print(Debug.GC(true).ToString()); + try + { + if (_e.URL == "SaveConfig") + { + FileStream fs = new FileStream(Settings.LAST_POST, FileMode.Create, FileAccess.Write); + fs.Write(_buffer,0,_buffer.Length); + fs.Flush(); + fs.Close(); + } + } + catch (Exception ex) + { + Debug.Print("Error writing POST-data"); + return false; + } + return true; + } + #region IDisposable Members + + public void Dispose() + { + _buffer = new byte[0]; + } + + #endregion + } +} diff --git a/WebServer/Properties/AssemblyInfo.cs b/WebServer/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..eb33686 --- /dev/null +++ b/WebServer/Properties/AssemblyInfo.cs @@ -0,0 +1,25 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("WebServer")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("NRCan-RNCan")] +[assembly: AssemblyProduct("WebServer")] +[assembly: AssemblyCopyright("Copyright © NRCan-RNCan 2012")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/WebServer/Request.cs b/WebServer/Request.cs index cdf1d99..8864d9c 100644 --- a/WebServer/Request.cs +++ b/WebServer/Request.cs @@ -1,59 +1,135 @@ -using System; -using Microsoft.SPOT; -using System.Net.Sockets; -using System.Collections; -using System.Text; - -namespace WebServer -{ - public enum RequestType { Get, Post } - - public class Request : IDisposable - { - const int FileBufferSize = 256; - - public RequestType RequestType { get; protected set; } - public Socket Client { get; protected set; } - public string Uri { get; protected set; } - public string BaseUri { get; protected set; } - public Hashtable HeaderFields { get; protected set; } - public Hashtable Querystring { get; protected set; } - - public Request(Socket _client, byte[] _data) - { - Client = _client; - string data = new string(Encoding.UTF8.GetChars(_data)); - string[] requestArray = data.Split('\n'); - if (requestArray.Length > 1) - { - // split fist line to get URI information and Request type - string[] split = requestArray[0].Split(' '); - if (split.Length >= 2) - { - // get request type - split[0] = split[0].ToUpper(); - switch (split[0]) - { - case "GET": - RequestType = WebServer.RequestType.Get; - break; - case "POST": - RequestType = WebServer.RequestType.Post; - break; - default: - throw new NotImplementedException("Can only handle GET and POST"); - } - // Now that we have the URI, parse out the query string - Uri = split[1]; - string baseUri = ""; - Querystring = HttpGeneral.ParseQuerystring(Uri, out baseUri); - BaseUri = baseUri; - } - - if (split.Length >= 3) HeaderFields.Add("HttpVersion", split[2]); - } - } - - public void Dispose() { } - } -} +using System; +using System.Text; +using System.Net.Sockets; +using System.Net; +using System.Diagnostics; +using System.IO; +using System.Collections; + +namespace Webserver +{ + //ToDo: Post-Values have to be copied into hashtable "postContent" + + public class Request : IDisposable + { + protected string _method; + protected string _url; + protected Hashtable _postArguments = new Hashtable(); + protected Hashtable _getArguments = new Hashtable(); + + /// + /// Hashtable with all GET key-value pa in it + /// + public Hashtable GetArguments + { + get { return _getArguments; } + private set { _getArguments = value; } + } + + /// + /// HTTP verb (Request method) + /// + public string Method + { + get { return _method; } + private set { _method = value; } + } + + /// + /// URL of request without GET values + /// + public string URL + { + get { return _url; } + private set + { + //if just the server address is requested, return the default page. + if (value == "") + _url = "index.html"; + else + _url = value; + } + } + + /// + /// ToDo: Post-Values have to be copied into hashtable + /// Full POST line is saved to "post"-key + /// + public Hashtable PostContent + { + get { return _postArguments; } + } + + /// + /// Creates request + /// + /// Input from network + public Request(char[] Data) + { + ProcessRequest(Data); + } + + /// + /// Sets up the request + /// + /// Input from network + private void ProcessRequest(char[] data) + { + string content = new string(data); + string htmlHeader1stLine = content.Substring(0, content.IndexOf('\n')); + + // Parse the first line of the request: "GET /path/ HTTP/1.1" + string[] headerParts = htmlHeader1stLine.Split(' '); + _method = headerParts[0]; + string[] UrlAndParameters = headerParts[1].Split('?'); + this.URL = UrlAndParameters[0].Substring(1); // Substring to ignore the '/' + + if (_method.ToUpper() == "GET" && UrlAndParameters.Length > 1) + { + FillGETHashtable(UrlAndParameters[1]); + } + + if (_method.ToUpper() == "POST") // TODO! + { + int lastLine = content.LastIndexOf('\n'); + _postArguments.Clear(); + _postArguments.Add("post", content.Substring(lastLine + 1, content.Length - lastLine)); + } + else + _postArguments = null; + + // Could look for any further headers in other lines of the request if required (e.g. User-Agent, Cookie) + } + + /// + /// builds arguments hash table + /// + /// + private void FillGETHashtable(string url) + { + _getArguments = new Hashtable(); + + string[] urlArguments = url.Split('&'); + string[] keyValuePair; + + for (int i = 0; i < urlArguments.Length; i++) + { + keyValuePair = urlArguments[i].Split('='); + _getArguments.Add(keyValuePair[0], keyValuePair[1]); + } + } + + #region IDisposable Members + + public void Dispose() + { + if(_postArguments != null) + _postArguments.Clear(); + + if(_getArguments != null) + _getArguments.Clear(); + } + + #endregion + } +} \ No newline at end of file diff --git a/WebServer/RequestReceivedEventArgs.cs b/WebServer/RequestReceivedEventArgs.cs new file mode 100644 index 0000000..1d71192 --- /dev/null +++ b/WebServer/RequestReceivedEventArgs.cs @@ -0,0 +1,44 @@ +using System; +using System.Text; +using System.Net.Sockets; +using System.Net; + +namespace NeonMika.Webserver.EventArgs +{ + public class RequestReceivedEventArgs + { + private DateTime receiveTime; + private Request request; + private Socket client; + private int byteCount; + + public DateTime ReceiveTime + { + get { return receiveTime; } + } + + public Request Request + { + get { return request; } + } + + public Socket Client + { + get { return client; } + } + + public int ByteCount + { + get { return byteCount; } + } + + public RequestReceivedEventArgs(Request request, Socket client, int byteCount) + { + this.request = request; + this.client = client; + this.byteCount = byteCount; + + this.receiveTime = DateTime.Now; + } + } +} diff --git a/WebServer/Response.cs b/WebServer/Response.cs deleted file mode 100644 index 1443e9d..0000000 --- a/WebServer/Response.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; -using Microsoft.SPOT; - -namespace WebServer -{ - class Response - { - } -} diff --git a/WebServer/Responses/FileResponse.cs b/WebServer/Responses/FileResponse.cs new file mode 100644 index 0000000..9e943ac --- /dev/null +++ b/WebServer/Responses/FileResponse.cs @@ -0,0 +1,95 @@ +using System; +using System.Text; +using System.IO; +using System.Net.Sockets; +using Webserver.EventArgs; +using Microsoft.SPOT; + +namespace Webserver.Responses +{ + /// + /// Standard response sending file to client + /// + public class FileResponse : Response + { + public FileResponse(String name = "FileResponse") + : base(name) + { } + + public override bool ConditionsCheckAndDataFill(RequestReceivedEventArgs RequestArguments) + { + return true; + } + + public override bool SendResponse(RequestReceivedEventArgs RequestArguments) + { + StringBuilder sb = new StringBuilder(); + sb.Append(RequestArguments.Request.URL); + sb.Replace("/","\\"); + string filePath = Settings.ROOT_PATH + sb.ToString(); + + + //File found check + try + { + if (!File.Exists(filePath)) + { + Send404_NotFound(RequestArguments.Client); + return false; + } + } + catch (Exception ex) + { + Debug.Print(ex.ToString()); + return false; + } + + string mType = MimeType(filePath); + + //File sending + using (FileStream inputStream = new FileStream(filePath, FileMode.Open)) + { + Send200_OK(mType, (int)inputStream.Length, RequestArguments.Client); + + byte[] readBuffer = new byte[Settings.FILE_BUFFERSIZE]; + int sentBytes = 0; + + //Sending parts in size of "Settings.FILE_BUFFERSIZE" + while (sentBytes < inputStream.Length) + { + int bytesRead = inputStream.Read(readBuffer, 0, readBuffer.Length); + try + { + if (SocketConnected(RequestArguments.Client)) + { + sentBytes += RequestArguments.Client.Send(readBuffer, bytesRead, SocketFlags.None); + } + else + { + RequestArguments.Client.Close(); + return false; + } + } + catch (Exception ex) + { + Debug.Print(ex.ToString()); + try + { + RequestArguments.Client.Close(); + } + catch (Exception ex2) + { + Debug.Print(ex2.ToString()); + } + + return false; + } + } + } + + return true; + } + } +} + + \ No newline at end of file diff --git a/WebServer/Responses/Response.cs b/WebServer/Responses/Response.cs new file mode 100644 index 0000000..0a9b8bc --- /dev/null +++ b/WebServer/Responses/Response.cs @@ -0,0 +1,155 @@ +using System; +using System.Text; +using System.Diagnostics; +using System.Net.Sockets; +using System.IO; +using System.Collections; +using Webserver.EventArgs; +using Microsoft.SPOT; + +namespace Webserver.Responses +{ + abstract public class Response : IDisposable + { + private string _url; + + public string URL + { + get { return _url; } + set { _url = value; } + } + + /// + /// Creates response to send back to client + /// + /// Webserver expand methods + public Response(string Name) + { + this._url = Name; + } + + /// + /// Creates header for 200 OK response + /// + /// MIME type of response + /// Byte count of response body + protected void Send200_OK(string MimeType, int ContentLength, Socket Client) + { + /* + StringBuilder headerBuilder = new StringBuilder(); + headerBuilder.Append("HTTP/1.0 200 OK\r\n"); + headerBuilder.Append("Content-Type: "); + headerBuilder.Append(MimeType); + headerBuilder.Append("; charset=utf-8\r\n"); + headerBuilder.Append("Content-Length: "); + headerBuilder.Append(ContentLength.ToString()); + headerBuilder.Append("\r\n"); + headerBuilder.Append("Connection: close\r\n\r\n"); + * */ + + String header = "HTTP/1.0 200 OK\r\n" + "Content-Type: " + MimeType + "; charset=utf-8\r\n" + "Content-Length: " + ContentLength.ToString() + "\r\n" + "Connection: close\r\n\r\n"; + + try + { + Client.Send(Encoding.UTF8.GetBytes(header), header.Length, SocketFlags.None); + } + catch (Exception e) + { + Debug.Print(e.Message.ToString()); + return; + } + } + + /// + /// Sends a 404 Not Found response + /// + protected void Send404_NotFound(Socket Client) + { + string header = "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\nConnection: close\r\n\r\nWebserver is sorry, but:\nPage not found"; + if (Client != null) + Client.Send(Encoding.UTF8.GetBytes(header), header.Length, SocketFlags.None); + Debug.Print("Sent 404 Not Found"); + } + + /// + /// + /// + /// File name or complete file path + /// MIME type + protected string MimeType(string Filename) + { + string result = "text/html"; + int dot = Filename.LastIndexOf('.'); + + string ext = (dot >= 0) ? Filename.Substring(dot + 1) : ""; + switch (ext) + { + case "htm": + case "html": + result = "text/html"; + break; + case "js": + result = "text/javascript"; + break; + case "css": + result = "text/css"; + break; + case "xml": + case "xsl": + result = "text/xml"; + break; + case "jpg": + case "jpeg": + result = "image/jpeg"; + break; + case "gif": + result = "image/gif"; + break; + case "png": + result = "image/png"; + break; + case "ico": + result = "x-icon"; + break; + case "mid": + result = "audio/mid"; + break; + default: + result = "application/octet-stream"; + break; + } + return result; + } + + protected bool SocketConnected(Socket s) + { + bool part1 = s.Poll(1000, SelectMode.SelectRead); + bool part2 = (s.Available == 0); + if (part1 & part2) + return false; + else + return true; + } + + /// + /// Override this method to implement a response logic. + /// + /// True if Response was sent, false if not + abstract public bool SendResponse(RequestReceivedEventArgs requestArguments); + + /// + /// Override this, check the URL and process data if needed + /// + /// True if SendResponse should be sent, false if not + abstract public bool ConditionsCheckAndDataFill(RequestReceivedEventArgs requestArguments); + + #region IDisposable Members + + public void Dispose() + { + + } + + #endregion + } +} diff --git a/WebServer/Responses/ResponseErrorType.cs b/WebServer/Responses/ResponseErrorType.cs new file mode 100644 index 0000000..88fc5a0 --- /dev/null +++ b/WebServer/Responses/ResponseErrorType.cs @@ -0,0 +1,19 @@ +using System; +using System.Text; +using System.Diagnostics; +using System.Net.Sockets; +using System.IO; +using System.Collections; +using Webserver.EventArgs; +using Microsoft.SPOT; + +namespace Webserver.Responses +{ + public enum ResponseErrorType + { + ParameterMissing, + ParameterConvertError, + ParameterRangeException, + InternalValueNotSet + } +} \ No newline at end of file diff --git a/WebServer/Responses/XMLResponse.cs b/WebServer/Responses/XMLResponse.cs new file mode 100644 index 0000000..f13d366 --- /dev/null +++ b/WebServer/Responses/XMLResponse.cs @@ -0,0 +1,101 @@ +using System; +using System.Text; +using System.IO; +using System.Net.Sockets; +using System.Collections; +using Webserver.EventArgs; +using Microsoft.SPOT; + +namespace Webserver.Responses +{ + public class XMLResponse : Response + { + public XMLResponse(string url, XMLResponseMethod method) + : base(url) + { + this._ResponseMethod = method; + _Pairs = new Hashtable(); + } + + private XMLResponseMethod _ResponseMethod; + private Hashtable _Pairs; + + public static Hashtable GenerateErrorHashtable(String parameter, ResponseErrorType ret) + { + Hashtable h = new Hashtable(); + + switch (ret) + { + case ResponseErrorType.ParameterConvertError: + h.Add("error","Following parameter could not be converted: " + parameter + " to the right format (int, string, ...)."); + break; + case ResponseErrorType.ParameterMissing: + h.Add("error","Following parameter was not submitted: " + parameter + ". Please include it in your URL"); + break; + case ResponseErrorType.InternalValueNotSet: + h.Add("error","An internal error accured. Following value could not be set: " + parameter + ". Please check the requested method's source code"); + break; + case ResponseErrorType.ParameterRangeException: + h.Add("error","Following parameter was out of range: " + parameter + "."); + break; + } + + return h; + } + + /// + /// Execute this to check if SendResponse shoul be executed + /// + /// Event Args + /// True if URL refers to this method, otherwise false (false = SendRequest should not be exicuted) + public override bool ConditionsCheckAndDataFill(RequestReceivedEventArgs RequestArguments) + { + _Pairs.Clear(); + if (RequestArguments.Request.URL == this.URL) + _ResponseMethod(RequestArguments, _Pairs); + else + return false; + return true; + } + + + public void SetError() + { } + + /// + /// Sends XML to client + /// + /// Could be null + /// True if 200_OK was sent, otherwise false + public override bool SendResponse(RequestReceivedEventArgs requestArguments) + { + String xml = ""; + xml += ""; + + xml += ""; + + //xml += XMLConverter.ConvertHashtableToXMLString(_Pairs); + + xml += ""; + + byte[] bytes = Encoding.UTF8.GetBytes(xml); + + int byteCount = bytes.Length; + + try + { + Send200_OK("text/xml", byteCount, requestArguments.Client); + requestArguments.Client.Send(bytes, byteCount, SocketFlags.None); + } + catch (Exception ex) + { + Debug.Print(ex.ToString()); + return false; + } + + return true; + } + + + } +} diff --git a/WebServer/Server.cs b/WebServer/Server.cs new file mode 100644 index 0000000..e4e5e87 --- /dev/null +++ b/WebServer/Server.cs @@ -0,0 +1,483 @@ +using System; +using System.Collections; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using Microsoft.SPOT; +using Microsoft.SPOT.Hardware; +using Microsoft.SPOT.Net.NetworkInformation; +using Webserver.EventArgs; +using Webserver.Responses; +using NeonMika.Webserver.POST; + +namespace Webserver +{ + /// + /// XML Expansion methods have to be in this form + /// + /// Access to GET or POST arguments,... + /// This hashtable gets converted into xml on response + public delegate void XMLResponseMethod(RequestReceivedEventArgs e, Hashtable results); + + + public class Server + { + public int _PortNumber { get; private set; } + private Socket _ListeningSocket = null; + private Hashtable _Responses = new Hashtable(); + + + /// + /// Creates an instance running in a seperate thread + /// + /// The port to listen + public Server(int portNumber = 80, bool DhcpEnable = false) + { + var interf = NetworkInterface.GetAllNetworkInterfaces()[0]; + + if (DhcpEnable) + { + interf.EnableDhcp(); + interf.RenewDhcpLease(); + } + + Debug.Print("Webserver is running on " + interf.IPAddress + " /// DHCP: " + interf.IsDhcpEnabled); + + this._PortNumber = portNumber; + ResponseListInitialize(); + + _ListeningSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + _ListeningSocket.Bind(new IPEndPoint(IPAddress.Any, portNumber)); + _ListeningSocket.SendTimeout = 10000; + _ListeningSocket.ReceiveTimeout = 10000; + _ListeningSocket.Listen(4); + + var webserverThread = new Thread(WaitingForRequest); + webserverThread.Start(); + } + + /// + /// Waiting for client to connect. + /// When bytes were read they get wrapped to a "Reqeust" and packed into a "RequestReceivedEventArgs" + /// + private void WaitingForRequest() + { + while (true) + { + try + { + using (Socket clientSocket = _ListeningSocket.Accept()) + { + Debug.Print("Client connected"); + + int availableBytes = 0; + int LoopCount = 0; + Thread.Sleep(100); + //if not all incoming bytes were received by the socket + do + { + if (availableBytes < clientSocket.Available) + { + availableBytes = clientSocket.Available; + LoopCount = 0; + } + else + LoopCount += 1; + Thread.Sleep(1); + } while (availableBytes == 0 || LoopCount < 400); + + //ignore requests that are too big + if (availableBytes < Settings.MAX_REQUESTSIZE && availableBytes > 0) + { + byte[] buffer = new byte[availableBytes]; + int readByteCount = clientSocket.Receive(buffer, availableBytes, SocketFlags.None); + + //reqeust created, checking the response possibilities + using (Request tempRequest = new Request(Encoding.UTF8.GetChars(buffer))) + { + Debug.Print("Final byte count: " + availableBytes + " | Request URL=" + tempRequest.URL); + + if (tempRequest.Method == "POST") + { + //POST was incoming, it will be saved to SD card at Settings.POST_TEMP_PATH + PostToSdWriter post = new PostToSdWriter(tempRequest, buffer); + post.Receive(); + } + + RequestReceivedEventArgs e = new RequestReceivedEventArgs(tempRequest, clientSocket, availableBytes); + HandleRequest(e); + } + Debug.Print("Request destroyed"); + } + else + { + } + + Debug.Print("Client loop finished"); + + try + { + clientSocket.Close(); + } + catch (Exception ex) + { + Debug.Print(ex.ToString()); + } + } + } + catch (Exception ex) + { + Debug.Print(ex.Message); + } + } + } + + /// + /// Checks an incoming request against the possible responses + /// + /// + private void HandleRequest(RequestReceivedEventArgs e) + { + Debug.Print("Start checking requests"); + Response response = null; + + if (_Responses.Contains(e.Request.URL)) + { + response = (Response)_Responses[e.Request.URL]; + } + else + { + response = (Response)_Responses["FileResponse"]; + } + + + if (response != null) + { + if (response.ConditionsCheckAndDataFill(e)) + { + if (!response.SendResponse(e)) + Debug.Print("Sending response failed"); + } + } + + Debug.Print("Request handling finnished"); + } + + //------------------------------------------------------------- + //------------------------------------------------------------- + //---------------Webserver expansion--------------------------- + //------------------------------------------------------------- + //------------------------------------------------------------- + //-------------------Basic methods----------------------------- + + /// + /// Adds a Response + /// + /// XMLResponse that has to be added + public void AddResponse(Response response) + { + if (!_Responses.Contains(response.URL)) + { + _Responses.Add(response.URL, response); + } + } + + /// + /// Removes a Response + /// + /// XMLResponse that has to be deleted + public void RemoveResponse(String ResponseName) + { + if (_Responses.Contains(ResponseName)) + { + _Responses.Remove(ResponseName); + } + } + + //------------------------------------------------------------- + //------------------------------------------------------------- + //-----------------------EXPAND this methods------------------- + + /// + /// Initialize the basic functionalities of the webserver + /// + private void ResponseListInitialize() + { + AddResponse(new FileResponse()); + AddResponse(new XMLResponse("echo", new XMLResponseMethod(Echo))); + //AddResponse(new XMLResponse("switchDigitalPin", new XMLResponseMethod(SwitchDigitalPin))); + //AddResponse(new XMLResponse("setDigitalPin", new XMLResponseMethod(SetDigitalPin))); + //AddResponse(new XMLResponse("xmlResponselist", new XMLResponseMethod(ResponseListXML))); + //AddResponse(new JSONResponse("jsonResponselist", new JSONResponseMethod(ResponseListJSON))); + //AddResponse(new XMLResponse("pwm", new XMLResponseMethod(SetPWM))); + //AddResponse(new XMLResponse("getAnalogPinValue", new XMLResponseMethod(GetAnalogPinValue))); + //AddResponse(new XMLResponse("getDigitalPinState", new XMLResponseMethod(GetDigitalPinState))); + //AddResponse(new XMLResponse("multixml", new XMLResponseMethod(MultipleXML))); + //AddResponse(new IndexResponse()); + } + + //------------------------------------------------------------- + //---------------------Expansion Methods----------------------- + //------------------------------------------------------------- + //----------Look at the echo method for xml example------------ + + /// + /// Example for webserver expand method + /// Call via http://servername/echo?value='echovalue' + /// Submit a 'value' GET parameter + /// + /// + /// + /// + private void Echo(RequestReceivedEventArgs e, Hashtable results) + { + if (e.Request.GetArguments.Contains("value") == true) + results.Add("echo", e.Request.GetArguments["value"]); + else + results.Add("ERROR", "No 'value'-parameter transmitted to server"); + } + + /// + /// Submit a 'pin' GET parameter to switch an OutputPorts state (on/off) + /// + /// + /// + /// + /* private static void SwitchDigitalPin(RequestReceivedEventArgs e, Hashtable h) + { + if (e.Request.GetArguments.Contains("pin")) + try + { + int pin = Int32.Parse(e.Request.GetArguments["pin"].ToString()); + if (pin >= 0 && pin <= 13) + { + PinManagement.SwitchDigitalPinState(pin); + h.Add("pin" + pin, PinManagement.GetDigitalPinState(pin) ? "1" : "0"); + } + } + catch + { + h = XMLResponse.GenerateErrorHashtable("pin", ResponseErrorType.ParameterConvertError); + } + else + h = XMLResponse.GenerateErrorHashtable("pin", ResponseErrorType.ParameterMissing); + }*/ + + /// + /// Submit a 'pin' (0-13) and a 'state' (true/false) GET parameter to turn on/off OutputPort + /// + /// + /// + /// + /* private static void SetDigitalPin(RequestReceivedEventArgs e, Hashtable h) + { + if (e.Request.GetArguments.Contains("pin")) + if (e.Request.GetArguments.Contains("state")) + try + { + int pin = Int32.Parse(e.Request.GetArguments["pin"].ToString()); + if (pin >= 0 && pin <= 13) + { + try + { + bool state = (e.Request.GetArguments["state"].ToString() == "true") ? true : false; + PinManagement.SetDigitalPinState(pin, state); + h.Add("pin" + pin, PinManagement.GetDigitalPinState(pin) ? "1" : "0"); + } + catch + { + h = XMLResponse.GenerateErrorHashtable("state", ResponseErrorType.ParameterRangeException); + } + } + else + h = XMLResponse.GenerateErrorHashtable("pin", ResponseErrorType.ParameterRangeException); + } + catch + { + h = XMLResponse.GenerateErrorHashtable("pin", ResponseErrorType.ParameterConvertError); + } + else + h = XMLResponse.GenerateErrorHashtable("state", ResponseErrorType.ParameterMissing); + else + h = XMLResponse.GenerateErrorHashtable("pin", ResponseErrorType.ParameterMissing); + }*/ + + /// + /// Returns the responses added to the webserver + /// + /// + /// + /// + private void ResponseListXML(RequestReceivedEventArgs e, Hashtable h) + { + foreach (Object k in _Responses.Keys) + { + if (_Responses[k] as XMLResponse != null) + { + h.Add("methodURL", k.ToString()); + } + } + } + + /* + /// + /// Returns the responses added to the webserver + /// + /// + /// + /// + private void ResponseListJSON(RequestReceivedEventArgs e, JsonArray j) + { + JsonObject o; + foreach (Object k in _Responses.Keys) + { + if (_Responses[k] as JSONResponse != null) + { + o = new JsonObject(); + o.Add("methodURL", k); + o.Add("methodInternalName", ((Response)_Responses[k]).URL); + j.Add(o); + } + } + } + */ + + + /// + /// Submit a 'pin' (5,6,9,10), a period and a duration (0 for off, period-value for 100% on) GET parameter to control PWM + /// + /// + /// + /// + /*private void SetPWM(RequestReceivedEventArgs e, Hashtable h) + { + if (e.Request.GetArguments.Contains("pin")) + { + if (e.Request.GetArguments.Contains("period")) + { + if (e.Request.GetArguments.Contains("duration")) + { + try + { + int pin = Int32.Parse(e.Request.GetArguments["pin"].ToString()); + try + { + uint duration = UInt32.Parse(e.Request.GetArguments["duration"].ToString()); + try + { + uint period = UInt32.Parse(e.Request.GetArguments["period"].ToString()); + if (PinManagement.SetPWM(pin, period, duration)) + h.Add("success", period + "/" + duration); + else + h = XMLResponse.GenerateErrorHashtable("PWM", ResponseErrorType.InternalValueNotSet); + } + catch (Exception ex) + { + h = XMLResponse.GenerateErrorHashtable("period", ResponseErrorType.ParameterConvertError); + Debug.Print(ex.ToString()); + } + } + catch (Exception ex) + { + h = XMLResponse.GenerateErrorHashtable("duration", ResponseErrorType.ParameterConvertError); + Debug.Print(ex.ToString()); + } + } + catch (Exception ex) + { + h = XMLResponse.GenerateErrorHashtable("pin", ResponseErrorType.ParameterConvertError); + Debug.Print(ex.ToString()); + } + } + else + h = XMLResponse.GenerateErrorHashtable("duration", ResponseErrorType.ParameterMissing); + } + else + h = XMLResponse.GenerateErrorHashtable("period", ResponseErrorType.ParameterMissing); + } + else + h = XMLResponse.GenerateErrorHashtable("pin", ResponseErrorType.ParameterMissing); + }*/ + + /// + /// Submit a 'pin' (0-13) GET parameter. Returns true or false + /// + /// + /// + /// + /*private void GetDigitalPinState(RequestReceivedEventArgs e, Hashtable h) + { + if (e.Request.GetArguments.Contains("pin")) + { + try + { + int pin = Int32.Parse(e.Request.GetArguments["pin"].ToString()); + h.Add("pin" + pin, PinManagement.GetDigitalPinState(pin) ? "1" : "0"); + } + catch (Exception ex) + { + h = XMLResponse.GenerateErrorHashtable("pin", ResponseErrorType.ParameterConvertError); + Debug.Print(ex.ToString()); + } + } + else + h = XMLResponse.GenerateErrorHashtable("pin", ResponseErrorType.ParameterMissing); + }*/ + + /// + /// Submit a 'pin' (0-5) GET parameter. Returns true or false + /// + /// + /// + /// + /*private void GetAnalogPinValue(RequestReceivedEventArgs e, Hashtable h) + { + if (e.Request.GetArguments.Contains("pin")) + { + try + { + int pin = Int32.Parse(e.Request.GetArguments["pin"].ToString()); + h.Add("pin" + pin, PinManagement.GetAnalogPinValue(pin)); + } + catch (Exception ex) + { + h = XMLResponse.GenerateErrorHashtable("pin", ResponseErrorType.ParameterConvertError); + Debug.Print(ex.ToString()); + } + } + else + h = XMLResponse.GenerateErrorHashtable("pin", ResponseErrorType.ParameterMissing); + }*/ + + /// + /// Example for the useage of the new XML library + /// Use the hashtable if you don't need nested XML (like the standard xml responses) + /// If you need nested XML, use the XMLPair class. The Key-parameter is String. + /// As value the following types can be used to achieve nesting: XMLPair, XMLPair[] and Hashtable + /// + /// + /// + /// + /*private void MultipleXML(RequestReceivedEventArgs e, Hashtable returnHashtable) + { + returnHashtable.Add("UseTheHashtable", "If you don't need nested XML"); + + XMLPair[] Phones = new XMLPair[2]; + Phones[0] = new XMLPair("Phone"); + Phones[1] = new XMLPair("Phone"); + + XMLPair[] PhoneAttributes0 = new XMLPair[2]; + PhoneAttributes0[0] = new XMLPair("Type", "Nokia"); + PhoneAttributes0[1] = new XMLPair("AvailableColours", new XMLPair[] {new XMLPair("Colour","Cyan"), new XMLPair("Colour","Black")}); + Phones[0].Value = PhoneAttributes0; + + XMLPair[] PhoneAttributes1 = new XMLPair[2]; + PhoneAttributes1[0] = new XMLPair("Type", "HTC"); + PhoneAttributes1[1] = new XMLPair("AvailableColours", new XMLPair("Colour", "Grey")); + Phones[1].Value = PhoneAttributes0; + + returnHashtable.Add("Phones", Phones); + }*/ + } +} diff --git a/WebServer/Settings.cs b/WebServer/Settings.cs new file mode 100644 index 0000000..699b3f9 --- /dev/null +++ b/WebServer/Settings.cs @@ -0,0 +1,32 @@ +using System; +using System.Text; + +namespace Webserver +{ + /// + /// Static settings for the webserver + /// + static class Settings + { + /// + /// "" for complete access, otherwise @"[drive]:\[folder]\[folder]\[...]\" + /// Watch the '\' at the end of the path + /// + public const string ROOT_PATH = "\\SD\\"; + + /// + /// Maximum byte size for a HTTP request sent to the server + /// + public const int MAX_REQUESTSIZE = 2048; + + /// + /// Buffersize for response file sending + /// + public const int FILE_BUFFERSIZE = 1024; + + /// + /// Temp Post Data File + /// + public const string LAST_POST = "\\SD\\LastPost.txt"; + } +} diff --git a/WebServer/WebResponseThread.cs b/WebServer/WebResponseThread.cs deleted file mode 100644 index e23bd5f..0000000 --- a/WebServer/WebResponseThread.cs +++ /dev/null @@ -1,336 +0,0 @@ -using System; -using System.Collections; -using System.IO; -using System.Net.Sockets; -using System.Text; -using System.Threading; -using Microsoft.SPOT; - -// This code has been modified from Freds Webserver, thanks Fred. -namespace WebServer -{ - public delegate string ResponseHandler(BaseRequest request); - public delegate void WorkFinishedHandler(Exception ex, WebResponseThread requestWorker); - public delegate void SocketAcceptedHandler(WebResponseThread requestWorker); - public delegate byte[] GetMoreBytesHandler(Socket connectionSocket, out int count); - - public class WebResponseThread - { - const int _LanPacketByteCount = 1024; - const int _PostRxBufferSize = 1024; - private Thread currentThread = null; - private ResponseHandler _ResponseHandler = null; - private WorkFinishedHandler _WorkFinishedHandler = null; - private SocketAcceptedHandler _SocketAcceptedHandler = null; - public int WorkerID = -1; - private Socket _ConnectionSocket = null; - - /// - /// Instantiates a new webthread. - /// - /// Port number to listen on. - public WebResponseThread(ResponseHandler responseHandler, WorkFinishedHandler workFinishedHandler, SocketAcceptedHandler socketAcceptedHandler) - { - _ResponseHandler = responseHandler; - _WorkFinishedHandler = workFinishedHandler; - _SocketAcceptedHandler = socketAcceptedHandler; - } - - /// - /// Start the server thread. - /// - public void Start(Socket nonBlockingSocket) - { - // start server - this.currentThread = new Thread(ThreadMain); - _ConnectionSocket = nonBlockingSocket.Accept();// connect in WebServer thread not Web response thread - currentThread.Start(); - Thread.Sleep(1); // context switch hopefully - Debug.Print("Started server in thread " + currentThread.GetHashCode().ToString()); - } - - /// - /// Allows an external process to stop the thread completely. - /// - public void Stop() - { - Debug.Print("Stopping this request."); - //bring down the thread. - this.currentThread.Abort(); - } - - /// - /// Runs the server in its own Thread. - /// - private void ThreadMain() - { - Exception workException = null; - try - { - Debug.Print("Accepted Socket, workerid: " + this.WorkerID.ToString()); - _SocketAcceptedHandler(this); - if (_ConnectionSocket.Poll(-1, SelectMode.SelectRead)) - { - // Create buffer and receive raw bytes. - - byte[] firstLanPacket = new byte[_LanPacketByteCount]; - int count = 0; - SocketFlags socketFlags = new SocketFlags(); - count = _ConnectionSocket.Receive(firstLanPacket, count, _ConnectionSocket.Available - count, socketFlags); - if (firstLanPacket[0] == 0) - { - Debug.Print("empty request, count: " + count.ToString()); - workException = new Exception("Empty Packet found" + this.WorkerID.ToString());// exception not thrown just sent back - } - else - { - string rawHeader = new string(Encoding.UTF8.GetChars(firstLanPacket)); - Hashtable headerFields = null; - int contentStart = -1; - EnumRequestType requestType = EnumRequestType.Get;// check for get first - BaseRequest.LoadHeader(firstLanPacket, count, out contentStart, out headerFields, out requestType); - BaseRequest request = null; - try - { - if (requestType == EnumRequestType.Get) - { - request = new BaseRequest(firstLanPacket, count, 0, headerFields, requestType);// no content - } - else - { - if (requestType == EnumRequestType.Post) - { - request = new PostRequest(firstLanPacket, count, 0, headerFields, requestType, _ConnectionSocket, GetMoreBytes);// todo put delegate - } - else - { - throw new NotImplementedException("Http PUT is not implemented."); - } - } - } - catch (Exception ex) - { - //return server 500; - workException = ex; - SendErrorResponse(_ConnectionSocket, SupportedErrors.ServerError, request, ex, this.WorkerID); - request = null; - } -// ConsoleWrite.CollectMemoryAndPrint(true, this.WorkerID); - //requestQueue.Enqueue(request);todo - if (request != null) - { - string requestMimeType = GetMimeType(request.Uri); - Debug.Print("Request MIME Type: " + requestMimeType); - if (requestMimeType.Length > 0) - { // file request - try - { - string fullFilePath = SDCard.GetFullPathFromUri(request.BaseUri); - Debug.Print("File Request received:" + fullFilePath); - // send start - long fileSize = SDCard.GetFileSize(fullFilePath); - if (fileSize > 0) - { - int maxAge = GetMaxAgeFromMimeType(requestMimeType); - string header = HttpGeneral.GetHttpHeader(fileSize, requestMimeType, maxAge); - byte[] returnBytes = Encoding.UTF8.GetBytes(header); - _ConnectionSocket.Send(returnBytes, returnBytes.Length, SocketFlags.None); - SDCard.ReadInChunks(fullFilePath, _ConnectionSocket);// boolean result doesnt really do much so ignore. - } - else - { - SendErrorResponse(_ConnectionSocket, SupportedErrors.FileNotFound, request, null, this.WorkerID); - } - } - catch (Exception ex) - { - //return server 500; - workException = ex; - SendErrorResponse(_ConnectionSocket, SupportedErrors.ServerError, request, ex, this.WorkerID); - } - } - else - {// page request - if (_ResponseHandler != null) - { - try - { - string response = _ResponseHandler(request); - byte[] returnBytes = Encoding.UTF8.GetBytes(response); - _ConnectionSocket.Send(returnBytes, 0, returnBytes.Length, SocketFlags.None); - } - catch (Exception ex) - { - //return server 500; - workException = ex; - SendErrorResponse(_ConnectionSocket, SupportedErrors.ServerError, request, ex, this.WorkerID); - } - } - } - } - } - } - }// using statement can throw in the dispose. - catch (Exception ex) - { - workException = ex; - Debug.Print("Exception, workerid: " + this.WorkerID.ToString() + ", ex: " + ex.Message); - } - finally - { - if (_ConnectionSocket != null) - { - ((IDisposable)_ConnectionSocket).Dispose(); - } - _WorkFinishedHandler(workException, this); - } - } - - private void SendErrorResponse(Socket connectionSocket, SupportedErrors errorType, BaseRequest request, Exception ex, int workerID) - { - string errorTypeText = ""; - if (errorType == SupportedErrors.ServerError) - { - errorTypeText = "500 Server Error"; - Debug.Print("SendErrorResponse, workerid: " + workerID.ToString() + ", ex: " + ex.Message + " " + errorTypeText); - } - else - { - errorTypeText = "404 File not found"; - Debug.Print("SendErrorResponse, workerid: " + workerID.ToString() + " " + errorTypeText); - } - string header = ""; - string content = HtmlGeneral.HtmlStart + "

" + errorTypeText + "

" + "

Uri: " + request.Uri + "

"; - if (ex != null) - { - content += "

Error: " + ex.Message + "

"; - } - content += HtmlGeneral.HtmlEnd; - if (errorType == SupportedErrors.ServerError) - { - header = HttpGeneral.Get500Header(content.Length); - } - else - { - header = HttpGeneral.Get404Header(content.Length); - } - byte[] returnBytes = Encoding.UTF8.GetBytes(header + content); - connectionSocket.Send(returnBytes, 0, returnBytes.Length, SocketFlags.None); - } - - public byte[] GetMoreBytes(Socket connectionSocket, out int count) - { - byte[] result = new byte[_PostRxBufferSize]; - SocketFlags socketFlags = new SocketFlags(); - count = connectionSocket.Receive(result, result.Length, socketFlags); - return result; - } - - public bool ReadInChunks(string fullPath, Socket socket) - { - Debug.Print("Reading file: " + fullPath); - bool chunkHasBeenRead = false; - int totalBytesRead = 0; - - if (File.Exists(fullPath)) - { - using (FileStream inputStream = new FileStream(fullPath, FileMode.Open)) - { - byte[] readBuffer = new byte[SDCard.READ_CHUNK_SIZE]; - while (true) - { - // Send the file a few bytes at a time - int bytesRead = inputStream.Read(readBuffer, 0, readBuffer.Length); - if (bytesRead == 0) - break; - socket.Send(readBuffer, 0, bytesRead, SocketFlags.None); - totalBytesRead += bytesRead; - } - } - chunkHasBeenRead = true; - } - else - { - chunkHasBeenRead = false; - } - - if (chunkHasBeenRead == true) - Debug.Print("Sending " + totalBytesRead.ToString() + " bytes..."); - else - Debug.Print("Failed to read chunk, full path: " + fullPath); - return chunkHasBeenRead; - } - - - public int GetMaxAgeFromMimeType(string mimeType) - { - int result = 7 * 24 * 60 * 60;// 1 week - if(mimeType == "text/plain" || mimeType == "text/html") - result = 10; - return result; - } - - - public string GetMimeType(string uri) - { - // Map the file extension to a mime type - string type = ""; - int questionMarkIndex = uri.IndexOf('?'); - string baseRequest = uri; - if (questionMarkIndex >= 0) - { - baseRequest = uri.Substring(0, questionMarkIndex); - } - int dot = baseRequest.LastIndexOf('.'); - if (dot >= 0) - { - switch (baseRequest.Substring(dot + 1)) - { - case "txt": - type = "text/plain"; - break; - case "css": - type = "text/css"; - break; - case "xml": - case "xsl": - type = "text/xml"; - break; - case "jpg": - case "jpeg": - type = "image/jpeg"; - break; - case "gif": - type = "image/gif"; - break; - case "htm": - case "html": - type = "text/html"; - break; - case "js": - type = "application/javascript"; - break; - - //todo json - // Not exhaustive. Extend this list as required. - } - } - return type; - } - - - - /// - /// Gets or sets the port the server listens on. - /// - public int Port { get; set; } - - /// - /// implement IDisposable - /// - public void Dispose() - { - Stop(); - } - } -} diff --git a/WebServer/WebServer.cs b/WebServer/WebServer.cs deleted file mode 100644 index 23d38c7..0000000 --- a/WebServer/WebServer.cs +++ /dev/null @@ -1,159 +0,0 @@ -using System; -using System.Net; -using System.Net.Sockets; -using System.Threading; -using Microsoft.SPOT; -using Microsoft.SPOT.Net.NetworkInformation; - - -namespace WebServer -{ - enum SupportedErrors { FileNotFound = 404, ServerError = 500 }; - public class WebServer : IDisposable - { - private Thread serverThread = null; - private WebResponseThreadList _ProcessRequestWorkerList = new WebResponseThreadList(); - private int _Port = -1; - int _ListeningID = -1;// -1 signifies not listening - bool _Stopping = false; - - #region Constructors - - /// - /// Instantiates a new webserver. - /// - /// Port number to listen on. - public WebServer(int port) - { - _Port = port; - Debug.Print("WebControl started on port " + port.ToString()); - } - - #endregion - - - /// - /// CommandReceived event is triggered when a valid command (plus parameters) is received. - /// Valid commands are defined in the AllowedCommands property. - /// - public event ResponseHandler ResponseHandler; - - - #region Public and private methods - - /// - /// Start the server thread. - /// - public void Start() - { - // start server - this.serverThread = new Thread(RunServer); - serverThread.Start(); - Debug.Print("Started server in thread " + serverThread.GetHashCode().ToString()); - } - - /// - /// Allows an external process to stop the web server completely. - /// - public void Stop() - { - _Stopping = true; - Debug.Print("Shutting down server."); - //bring down the thread. - //We have threads within threads (i.e. socket accept), so an elegant solution is a bigger change. - this.serverThread.Abort(); - } - - /// - /// Runs the server in its own Thread. - /// - private void RunServer() - { -// Debug.EnableGCMessages(true);// debug only - using (Socket nonBlockingSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) - { - nonBlockingSocket.Bind(new IPEndPoint(IPAddress.Any, _Port)); - nonBlockingSocket.Listen(1); - - while (!_Stopping) - { - if (_ListeningID == -1) - { - try - { - //_CurrentlyListening = true; - WebResponseThread currentProcessRequest = new WebResponseThread(ResponseHandler, WorkFinishedHandler, SocketAcceptedHandler); - while (_ListeningID < 0) - { - _ListeningID = _ProcessRequestWorkerList.Enqueue(currentProcessRequest); - if (_ListeningID < 0) - { - Debug.Print("no spare thread"); - } - Thread.Sleep(50); - } - currentProcessRequest.Start(nonBlockingSocket); - - } - catch (Exception ex) - {// probably lost connection - Debug.Print("Exception caught in Run Server: " + ex.Message); - _ProcessRequestWorkerList.RemoveAt(_ListeningID); - _ListeningID = -1;// what else to do if - Thread.Sleep(200);// - } - } - } - } - } - - private void WorkFinishedHandler(Exception ex, WebResponseThread requestWorker)// todo make thread safe - { - if (ex != null) - { - Debug.Print("WorkFinishedHandler exception: " + ex.Message); - } - _ProcessRequestWorkerList.RemoveAt(requestWorker.WorkerID); - } - - private void SocketAcceptedHandler(WebResponseThread requestWorker) - { - //_CurrentlyListening = false; - if (_ListeningID != requestWorker.WorkerID) - { - _ListeningID = -1; - throw (new Exception("_ListeningID != requestWorker.WorkerID")); - } - _ListeningID = -1; - } - - //private string DoResponseHandler(BaseRequest request) - //{ - // return ResponseHandler(request); - //} - - - public string ListInterfaces() - { - NetworkInterface[] ifaces = NetworkInterface.GetAllNetworkInterfaces(); - string s = string.Concat("Interfaces: "); - for (int i = 1; i <= ifaces.Length; i++) - { - NetworkInterface iface = ifaces[i-1]; - s += string.Concat(i.ToString(), ": ip ", iface.IPAddress, " subnet ", iface.SubnetMask); - } - return s; - } - #endregion - - /// - /// implement IDisposable - /// - public void Dispose() - { - _ProcessRequestWorkerList.StopAll(); - Thread.Sleep(10); - Stop(); - } - } -} diff --git a/WebServer/WebServer.csproj b/WebServer/WebServer.csproj index c3537ef..0efa28a 100644 --- a/WebServer/WebServer.csproj +++ b/WebServer/WebServer.csproj @@ -1,52 +1,56 @@ - - - - WebServer - Library - WebServer - {b69e3092-b931-443c-abe7-7e7b65f2a37f};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 9.0.21022 - 2.0 - {F444D76A-7C89-4AD5-B7B6-1B8293A0DC13} - v4.2 - $(MSBuildExtensionsPath32)\Microsoft\.NET Micro Framework\ - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - - - - - + + + + WebServer + Library + WebServer + {b69e3092-b931-443c-abe7-7e7b65f2a37f};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 9.0.21022 + 2.0 + {71B2DDB7-063C-4866-BD82-83D06E22E225} + v4.2 + $(MSBuildExtensionsPath32)\Microsoft\.NET Micro Framework\ + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file